source('../env.R')
Linking to GEOS 3.11.0, GDAL 3.5.3, PROJ 9.1.0; sf_use_s2() is TRUE
── Attaching core tidyverse packages ────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.4.4     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     ── Conflicts ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errorsRegistered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      

Attaching package: ‘dbplyr’

The following objects are masked from ‘package:dplyr’:

    ident, sql

Loading required package: ape

Attaching package: ‘ape’

The following object is masked from ‘package:dplyr’:

    where

Loading required package: maps

Attaching package: ‘maps’

The following object is masked from ‘package:purrr’:

    map

Please cite the eBird Status and Trends data using: 
  Fink, D., T. Auer, A. Johnston, M. Strimas-Mackey, S. Ligocki, O. Robinson, 
  W. Hochachka, L. Jaromczyk, C. Crowley, K. Dunham, A. Stillman, I. Davies, 
  A. Rodewald, V. Ruiz-Gutierrez, C. Wood. 2023.
  eBird Status and Trends, Data Version: 2022; Released: 2023. Cornell Lab of
  Ornithology, Ithaca, New York. https://doi.org/10.2173/ebirdst.2022

This version of the package provides access to the 2022 version of the eBird
Status and Trends Data Products. Access to the 2022 data will be provided 
until November 2024 when it will be replaced by the 2023 data. At that 
point, you will be required to update this R package and transition to using 
the new data.
terra 1.7.71

Attaching package: ‘terra’

The following object is masked from ‘package:phytools’:

    rescale

The following objects are masked from ‘package:ape’:

    rotate, trans, zoom

The following object is masked from ‘package:tidyr’:

    extract

Loading required package: permute
Loading required package: lattice
This is vegan 2.6-4

Attaching package: ‘vegan’

The following object is masked from ‘package:phytools’:

    scores

Loading required package: mvtnorm
Loading required package: survival
Loading required package: TH.data
Loading required package: MASS

Attaching package: ‘MASS’

The following object is masked from ‘package:terra’:

    area

The following object is masked from ‘package:dplyr’:

    select


Attaching package: ‘TH.data’

The following object is masked from ‘package:MASS’:

    geyser


Attaching package: ‘ggpubr’

The following object is masked from ‘package:terra’:

    rotate

The following object is masked from ‘package:ape’:

    rotate

Loading required package: nlme

Attaching package: ‘nlme’

The following object is masked from ‘package:dplyr’:

    collapse

Registered S3 method overwritten by 'GGally':
  method from   
  +.gg   ggplot2

Attaching package: ‘GGally’

The following object is masked from ‘package:terra’:

    wrap

ggtree v3.10.0 For help: https://yulab-smu.top/treedata-book/

If you use the ggtree package suite in published research, please cite the appropriate paper(s):

Guangchuang Yu, David Smith, Huachen Zhu, Yi Guan, Tommy Tsan-Yuk Lam. ggtree: an R package for visualization and annotation of phylogenetic
trees with their covariates and other associated data. Methods in Ecology and Evolution. 2017, 8(1):28-36. doi:10.1111/2041-210X.12628

Guangchuang Yu. Using ggtree to visualize data on tree-like structures. Current Protocols in Bioinformatics. 2020, 69:e96. doi:10.1002/cpbi.96

Guangchuang Yu.  Data Integration, Manipulation and Visualization of Phylogenetic Trees (1st edition). Chapman and Hall/CRC. 2022,
doi:10.1201/9781003279242 

Attaching package: ‘ggtree’

The following object is masked from ‘package:nlme’:

    collapse

The following object is masked from ‘package:ggpubr’:

    rotate

The following objects are masked from ‘package:terra’:

    flip, inset, rotate

The following object is masked from ‘package:ape’:

    rotate

The following object is masked from ‘package:tidyr’:

    expand


Attaching package: ‘foreach’

The following objects are masked from ‘package:purrr’:

    accumulate, when


Attaching package: ‘scales’

The following object is masked from ‘package:terra’:

    rescale

The following object is masked from ‘package:phytools’:

    rescale

The following object is masked from ‘package:purrr’:

    discard

The following object is masked from ‘package:readr’:

    col_factor

Spherical geometry (s2) switched off

Species in communities

It seems reasonable to expect that cities with simialr regional pools will have similar species entering the city, and thus a similar response to urbanisation.

Load data

city_effort = read_csv(filename(CITY_DATA_OUTPUT_DIR, 'city_effort.csv'))
Rows: 342 Columns: 7── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): city_name
dbl (6): city_id, total_city_checklists, total_city_locations, total_city_effort, total_city_area_m2, percentage_total_city_area_surveyed
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
city_effort
communities = read_csv(filename(COMMUNITY_OUTPUT_DIR, 'communities_for_analysis.csv'))
Rows: 2462 Columns: 12── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (5): city_name, jetz_species_name, seasonal, presence, origin
dbl (4): city_id, distance_to_northern_edge_km, distance_to_southern_edge_km, relative_abundance_proxy
lgl (3): present_urban_high, present_urban_med, present_urban_low
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
communities_summary = communities %>% group_by(city_id) %>% summarise(
  regional_pool_size = n(), 
  urban_pool_size = sum(relative_abundance_proxy > 0)
) %>% left_join(city_effort %>% dplyr::select(city_id, percentage_total_city_area_surveyed))
Joining with `by = join_by(city_id)`
ggplot(communities %>% filter(relative_abundance_proxy > 0), aes(x = relative_abundance_proxy)) + geom_bar(stat = "bin")

city_points = st_centroid(read_sf(filename(CITY_DATA_OUTPUT_DIR, 'city_selection.shp')))
Warning: st_centroid assumes attributes are constant over geometriesWarning: st_centroid does not give correct centroids for longitude/latitude data
community_data_metrics = read_csv(filename(COMMUNITY_OUTPUT_DIR, 'community_assembly_metrics_using_relative_abundance.csv')) %>%
  dplyr::select(city_id, mntd_normalised, fdiv_normalised, mass_fdiv_normalised, kipps_distance_fdiv_normalised, trophic_trait_fdiv_normalised, gape_width_fdiv_normalised) %>%
  left_join(read_csv(filename(CITY_DATA_OUTPUT_DIR, 'realms.csv'))) %>%
  left_join(communities_summary) %>%
  left_join(city_points[,c('city_id', 'city_nm')]) %>%
  rename(city_name='city_nm') %>%
  na.omit() %>%
  arrange(city_id)
Rows: 341 Columns: 43── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
dbl (43): mntd_normalised, mntd_actual, mntd_min, mntd_max, mntd_mean, mntd_sd, fdiv_normalised, fdiv_actual, fdiv_min, fdiv_max, fdiv_mean, fdiv_sd, mass_fdi...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 342 Columns: 2── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): core_realm
dbl (1): city_id
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(city_id)`Joining with `by = join_by(city_id)`Joining with `by = join_by(city_id)`
community_data_metrics
test_value = function(name, normalised_list) {
  wilcox_test_result = wilcox.test(normalised_list, mu = 0.5)
  
  significance = ifelse(wilcox_test_result$p.value < 0.0001, '***', 
                        ifelse(wilcox_test_result$p.value < 0.001, '**', 
                               ifelse(wilcox_test_result$p.value < 0.01, '*', '')))
  m = mean(normalised_list)
  
  paste(name, 'mean', round(m, 2), significance)
}
test_value('Global MNTD', community_data_metrics$mntd_normalised)
[1] "Global MNTD mean 0.5 "
test_value('Global beak gape', community_data_metrics$gape_width_fdiv_normalised)
[1] "Global beak gape mean 0.59 ***"
test_value('Global kipps distance', community_data_metrics$kipps_distance_fdiv_normalised)
[1] "Global kipps distance mean 0.75 ***"

Load trait data

traits = read_csv(filename(TAXONOMY_OUTPUT_DIR, 'traits_jetz.csv'))
Rows: 304 Columns: 6── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): jetz_species_name
dbl (5): gape_width, trophic_trait, locomotory_trait, mass, kipps_distance
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(traits)
fetch_normalised_traits = function(required_species_list) {
  required_traits = traits %>% filter(jetz_species_name %in% required_species_list)
  
  required_traits$gape_width_normalised = normalise(required_traits$gape_width, min(required_traits$gape_width), max(required_traits$gape_width))
  required_traits$kipps_distance_fdiv_normalised = normalise(required_traits$kipps_distance, min(required_traits$kipps_distance), max(required_traits$kipps_distance))
  required_traits$mass_normalised = normalise(required_traits$mass, min(required_traits$mass), max(required_traits$mass))
  
  traits_normalised_long = required_traits %>% pivot_longer(cols = c('gape_width_normalised', 'kipps_distance_fdiv_normalised', 'mass_normalised'), names_to = 'trait', values_to = 'normalised_value') %>% dplyr::select(jetz_species_name, trait, normalised_value)
  traits_normalised_long$trait = factor(traits_normalised_long$trait, levels = c('gape_width_normalised', 'kipps_distance_fdiv_normalised', 'mass_normalised'), labels = c('Gape Width', 'Kipps Distance', 'Mass'))
  
  traits_normalised_long
}

fetch_normalised_traits(c('Aplopelia_larvata', 'Chalcophaps_indica', 'Caloenas_nicobarica'))

Read in our phylogeny

phylo_tree = read.tree(filename(TAXONOMY_OUTPUT_DIR, 'phylogeny.tre'))
ggtree(phylo_tree, layout='circular')

Load resolve ecoregions

resolve = read_resolve()
Warning: st_buffer does not correctly buffer longitude/latitude datadist is assumed to be in decimal degrees (arc_degrees).
Warning: st_simplify does not correctly simplify longitude/latitude data, dTolerance needs to be in decimal degrees

Create helper functions

to_species_matrix = function(filtered_communities) {
  filtered_communities %>% 
    dplyr::select(city_id, jetz_species_name) %>% 
    distinct() %>%
    mutate(present = TRUE) %>% 
    pivot_wider(
      names_from = jetz_species_name, 
      values_from = "present", 
      values_fill = list(present = F)
    ) %>% 
    tibble::column_to_rownames(var='city_id')
}
community_nmds = function(filtered_communities) {
  species_matrix = to_species_matrix(filtered_communities)
  nmds <- metaMDS(species_matrix, k=2, trymax = 30)
  nmds_result = data.frame(vegan::scores(nmds)$sites)
  nmds_result$city_id = as.double(rownames(nmds_result))
  rownames(nmds_result) = NULL
  nmds_result
}

https://www.datacamp.com/tutorial/k-means-clustering-r

scree_plot = function(community_nmds_data) {
  # Decide how many clusters to look at
  n_clusters <- min(10, nrow(community_nmds_data) - 1)
  
  # Initialize total within sum of squares error: wss
  wss <- numeric(n_clusters)
  
  set.seed(123)
  
  # Look over 1 to n possible clusters
  for (i in 1:n_clusters) {
    # Fit the model: km.out
    km.out <- kmeans(community_nmds_data[,c('NMDS1','NMDS2')], centers = i, nstart = 20)
    # Save the within cluster sum of squares
    wss[i] <- km.out$tot.withinss
  }
  
  # Produce a scree plot
  wss_df <- tibble(clusters = 1:n_clusters, wss = wss)
   
  scree_plot <- ggplot(wss_df, aes(x = clusters, y = wss, group = 1)) +
      geom_point(size = 4) +
      geom_line() +
      geom_hline(linetype="dashed", color = "orange", yintercept = wss) +
      scale_x_continuous(breaks = c(2, 4, 6, 8, 10)) +
      xlab('Number of clusters')
  scree_plot
}
cluster_cities = function(city_nmds, cities_community_data, centers) {
  set.seed(123)
  kmeans_clusters <- kmeans(city_nmds[,c('NMDS1', 'NMDS2')], centers = centers, nstart = 20)
  city_nmds$cluster = kmeans_clusters$cluster
  cities_community_data %>% left_join(city_nmds) %>% mutate(cluster = as.factor(cluster))
}
plot_nmds_clusters = function(cluster_cities) {
  cluster_cities %>% dplyr::select(city_id, city_name, NMDS1, NMDS2, cluster) %>% distinct() %>%
  ggplot(aes(x = NMDS1, y = NMDS2, colour = cluster)) + geom_point() + geom_label_repel(aes(label = city_name))
}
get_presence_cell_width = function(city_cluster_data_metrics) {
  10 * length(unique(city_cluster_data_metrics$city_id))
}

get_presence_cell_height = function(city_cluster_data_metrics) {
  species = species_in_cluster = communities %>% 
    filter(city_id %in% city_cluster_data_metrics$city_id) %>% 
    dplyr::select(jetz_species_name) %>% 
    distinct()
  
  10 * nrow(species)
}

city_metric_height = 30
traits_width = 50
phylo_tree_width = 125
title_height = 8

get_image_height = function(city_cluster_data_metrics) {
  get_presence_cell_height(city_cluster_data_metrics) + (3 * city_metric_height) + title_height
}

get_image_width = function(city_cluster_data_metrics) {
  get_presence_cell_width(city_cluster_data_metrics) + traits_width + phylo_tree_width
}
species_in_city_label = function(species) {
  paste(
    ifelse(species$seasonal == 'Resident', '', substring(species$seasonal, 0, 1)),
    ifelse(species$origin == 'Native', '', substring(species$origin, 0, 1)),
    ifelse(species$distance_to_northern_edge_km > 200, '', paste('NRL', round(species$distance_to_northern_edge_km), sep = ' ')),
    ifelse(species$distance_to_southern_edge_km > 200, '', paste('SRL', round(species$distance_to_northern_edge_km), sep = ' ')),
    sep = '\n'
  )
}

species_in_city_label(head(communities))
[1] "\nI\n\n" "\nI\n\n" "\nI\n\n" "P\n\n\n" "\n\n\n"  "\nI\n\n"
plot_city_cluster = function(city_cluster_data_metrics, title) {
  species_in_cluster = communities %>% 
    filter(city_id %in% city_cluster_data_metrics$city_id) %>% 
    dplyr::select(jetz_species_name, city_name, relative_abundance_proxy, seasonal, origin, distance_to_northern_edge_km, distance_to_southern_edge_km)
  
  species_in_cluster$label = species_in_city_label(species_in_cluster)
  tree_cropped <- ladderize(drop.tip(phylo_tree, setdiff(phylo_tree$tip.label, species_in_cluster$jetz_species_name)))
    
  gg_tree = ggtree(tree_cropped)
  
  gg_presence = ggplot(species_in_cluster, aes(x=city_name, y=jetz_species_name)) + 
          geom_tile(aes(fill=relative_abundance_proxy)) + 
          geom_text(aes(label=label), size=0.75) +
          scale_fill_gradientn(colours=c("#98FB98", "#FFFFE0", "yellow", "orange", "#FF4500", "red", "red"), values=c(0, 0.00000000001, 0.1, 0.25, 0.5, 0.75, 1), na.value = "transparent") +
          theme_minimal() + xlab(NULL) + ylab(NULL) + 
          theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) + 
          labs(fill='Urban Proxy Abundance')
  
  species_in_cluster_traits = fetch_normalised_traits(species_in_cluster$jetz_species_name)
  
  gg_traits = ggplot(species_in_cluster_traits, aes(x = trait, y = jetz_species_name, size = normalised_value)) + geom_point() + theme_minimal() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1), axis.text.y=element_blank()) + xlab(NULL) + ylab(NULL) + labs(size = "Normalised Value")
  
  gg_cities_mntd = ggplot(city_cluster_data_metrics, aes(x = city_name, y = mntd_normalised)) + geom_bar(stat = "identity") + theme_minimal() + theme(legend.position = "none", axis.text.x=element_blank()) + xlab(NULL) + ylab("MNTD") + ylim(0, 1)
  
  gg_cities_gape_fd = ggplot(city_cluster_data_metrics, aes(x = city_name, y = gape_width_fdiv_normalised)) + geom_bar(stat = "identity") + theme_minimal() + theme(legend.position = "none", axis.text.x=element_blank()) + xlab(NULL) + ylab("Gape") + ylim(0, 1)
  
  gg_cities_loco_fd = ggplot(city_cluster_data_metrics, aes(x = city_name, y = kipps_distance_fdiv_normalised)) + geom_bar(stat = "identity") + theme_minimal() + theme(legend.position = "none", axis.text.x=element_blank()) + xlab(NULL) + ylab("Kipps") + ylim(0, 1)
  
  gg_title = ggplot() + labs(title = title, subtitle = paste(
    test_value('MNTD', city_cluster_data_metrics$mntd_normalised),
    test_value('FDiv', city_cluster_data_metrics$fdiv_normalised),
    test_value('Kipps distance', city_cluster_data_metrics$kipps_distance_fdiv_normalised),
    test_value('Gape width', city_cluster_data_metrics$gape_width_fdiv_normalised),
    sep = '\n'
  )) + theme_minimal() + theme(plot.subtitle=element_text(size=8, hjust=0, color="#444444"))
  
  gg_presence_height = get_presence_cell_height(city_cluster_data_metrics)
  gg_presence_width = get_presence_cell_width(city_cluster_data_metrics)
  
  gg_presence %>% insert_top(gg_cities_loco_fd, height = (city_metric_height / gg_presence_height)) %>% insert_top(gg_cities_gape_fd, height = (city_metric_height / gg_presence_height)) %>% insert_top(gg_cities_mntd, height = (city_metric_height / gg_presence_height)) %>% insert_left(gg_tree, width = (phylo_tree_width / gg_presence_width)) %>% insert_right(gg_traits, width = (traits_width / gg_presence_width)) %>% insert_top(gg_title, height = (title_height / gg_presence_height))
}
REGION_DEEP_DIVE_FIGURES_OUTPUT = mkdir(FIGURES_OUTPUT_DIR, 'appendix_regional_deep_dive_using_abundance')

Nearctic

nearctic_cities_community_data = community_data_metrics %>% filter(core_realm == 'Nearctic')
nearctic_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
 [1] "San Jose"                 "Los Angeles"              "Concord"                  "Tijuana"                  "Bakersfield"             
 [6] "Fresno"                   "Sacramento"               "Mexicali"                 "Hermosillo"               "Las Vegas"               
[11] "Phoenix"                  "Tucson"                   "Durango"                  "Portland"                 "Chihuahua"               
[16] "Aguascalientes"           "Seattle"                  "Ciudad Juárez"            "San Luis Potosí"          "Mexico City"             
[21] "Saltillo"                 "Vancouver"                "Salt Lake City"           "Albuquerque"              "Monterrey"               
[26] "Nuevo Laredo"             "San Antonio"              "Denver"                   "Austin"                   "Houston"                 
[31] "Dallas"                   "Oklahoma City"            "Calgary"                  "New Orleans"              "Kansas City"             
[36] "Omaha"                    "St. Louis"                "Bradenton"                "Tampa"                    "Minneapolis [Saint Paul]"
[41] "Atlanta"                  "Orlando"                  "Louisville"               "Chicago"                  "Indianapolis"            
[46] "Milwaukee"               

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
nearctic_cities_nmds = community_nmds(communities %>% filter(city_id %in% nearctic_cities_community_data$city_id)) 
Run 0 stress 0.1005678 
Run 1 stress 0.1017503 
Run 2 stress 0.1000046 
... New best solution
... Procrustes: rmse 0.007231564  max resid 0.03470212 
Run 3 stress 0.1000046 
... Procrustes: rmse 0.000004044592  max resid 0.00001391239 
... Similar to previous best
Run 4 stress 0.1205431 
Run 5 stress 0.1012776 
Run 6 stress 0.122422 
Run 7 stress 0.1005678 
Run 8 stress 0.1000046 
... Procrustes: rmse 0.000006352527  max resid 0.00001951675 
... Similar to previous best
Run 9 stress 0.1217145 
Run 10 stress 0.1000046 
... New best solution
... Procrustes: rmse 0.000003781293  max resid 0.00001247782 
... Similar to previous best
Run 11 stress 0.1217437 
Run 12 stress 0.1217437 
Run 13 stress 0.1000046 
... Procrustes: rmse 0.000004334204  max resid 0.00001530842 
... Similar to previous best
Run 14 stress 0.1000046 
... Procrustes: rmse 0.000004734417  max resid 0.00001660964 
... Similar to previous best
Run 15 stress 0.1012776 
Run 16 stress 0.1235457 
Run 17 stress 0.1000046 
... Procrustes: rmse 0.000004211501  max resid 0.00001320069 
... Similar to previous best
Run 18 stress 0.1005678 
Run 19 stress 0.1005678 
Run 20 stress 0.1017503 
*** Best solution repeated 4 times
nearctic_cities_nmds
scree_plot(nearctic_cities_nmds)

nearctic_cities = cluster_cities(city_nmds = nearctic_cities_nmds, cities_community_data = nearctic_cities_community_data, centers = 4)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(nearctic_cities)

nearctic_biomes = st_crop(resolve[resolve$REALM == 'Nearctic',c('REALM')], xmin = -220, ymin = 0, xmax = 0, ymax = 70)
although coordinates are longitude/latitude, st_intersection assumes that they are planar
Warning: attribute variables are assumed to be spatially constant throughout all geometries
 
ggplot() + 
  geom_sf(data = nearctic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = nearctic_cities, aes(geometry = geometry, color = cluster))

ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_clusters.jpg'))
Saving 7.29 x 4.51 in image

test_value('Nearctic MNTD', nearctic_cities$mntd_normalised)
Warning: cannot compute exact p-value with ties
[1] "Nearctic MNTD mean 0.6 "
test_value('Nearctic beak gape', nearctic_cities$gape_width_fdiv_normalised)
Warning: cannot compute exact p-value with ties
[1] "Nearctic beak gape mean 0.59 "
test_value('Nearctic Kipps distance', nearctic_cities$kipps_distance_fdiv_normalised)
Warning: cannot compute exact p-value with ties
[1] "Nearctic Kipps distance mean 0.58 "

Neartic Cluster 1`

nearactic_cluster1 = nearctic_cities %>% filter(cluster == 1)
plot_city_cluster(nearactic_cluster1, 'Neartic cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_cluster1.jpg'), width = get_image_width(nearactic_cluster1), height = get_image_height(nearactic_cluster1), units = "mm")

Neartic Cluster 2

nearactic_cluster2 = nearctic_cities %>% filter(cluster == 2)
plot_city_cluster(nearactic_cluster2, 'Neartic cluster 2')
Warning: cannot compute exact p-value with tiesWarning: cannot compute exact p-value with tiesWarning: cannot compute exact p-value with tiesWarning: cannot compute exact p-value with ties
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_cluster2.jpg'), width = get_image_width(nearactic_cluster2), height = get_image_height(nearactic_cluster2), units = "mm")

Neartic Cluster 3

nearactic_cluster3 = nearctic_cities %>% filter(cluster == 3)
plot_city_cluster(nearactic_cluster3, 'Neartic cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_cluster3.jpg'), width = get_image_width(nearactic_cluster3), height = get_image_height(nearactic_cluster3), units = "mm")

Neartic Cluster 4

nearactic_cluster4 = nearctic_cities %>% filter(cluster == 4)
plot_city_cluster(nearactic_cluster4, 'Neartic cluster 4')
Warning: cannot compute exact p-value with ties
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_cluster4.jpg'), width = get_image_width(nearactic_cluster4), height = get_image_height(nearactic_cluster4), units = "mm")

Neotropic

neotropic_cities_community_data = community_data_metrics %>% filter(core_realm == 'Neotropic')
neotropic_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
 [1] "Culiacán"                  "Guadalajara"               "Morelia"                   "Acapulco"                  "Querétaro"                
 [6] "Cuernavaca"                "Puebla"                    "Oaxaca"                    "Xalapa"                    "Veracruz"                 
[11] "Tuxtla Gutiérrez"          "Villahermosa"              "Guatemala City"            "San Salvador"              "San Pedro Sula"           
[16] "Mérida"                    "Tegucigalpa"               "Managua"                   "San José"                  "Cancún"                   
[21] "Guayaquil"                 "Chiclayo"                  "Panama City"               "Trujillo"                  "Quito"                    
[26] "Havana"                    "Cali"                      "Lima"                      "Pereira"                   "Miami"                    
[31] "Medellín"                  "Ibagué"                    "Cartagena"                 "Kingston"                  "Bogota"                   
[36] "Barranquilla"              "Bucaramanga"               "Cúcuta"                    "Maracaibo"                 "Arequipa"                 
[41] "Barquisimeto"              "Santo Domingo"             "Maracay"                   "El Alto [La Paz]"          "Caracas"                  
[46] "Cochabamba"                "Viña del Mar [Valparaíso]" "Río Piedras [San Juan]"    "Barcelona"                 "Concepción"               
[51] "Santiago"                  "Mendoza"                   "Salta"                     "Cordoba"                   "Asuncion"                 
[56] "Buenos Aires"              "La Plata"                  "Ciudad del Este"           "Montevideo"                "Mar del Plata"            
[61] "Porto Alegre"              "São Paulo"                 "Santos"                    "Sao Jose dos Campos"      

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
neotropic_cities_nmds = community_nmds(communities %>% filter(city_id %in% neotropic_cities_community_data$city_id)) 
Run 0 stress 0.134619 
Run 1 stress 0.134433 
... New best solution
... Procrustes: rmse 0.006824014  max resid 0.04572879 
Run 2 stress 0.1407144 
Run 3 stress 0.1348237 
... Procrustes: rmse 0.006646789  max resid 0.04604869 
Run 4 stress 0.1348046 
... Procrustes: rmse 0.006551306  max resid 0.04608394 
Run 5 stress 0.134433 
... Procrustes: rmse 0.00003293268  max resid 0.0001049247 
... Similar to previous best
Run 6 stress 0.134433 
... Procrustes: rmse 0.00005189183  max resid 0.0001643412 
... Similar to previous best
Run 7 stress 0.1366488 
Run 8 stress 0.1407144 
Run 9 stress 0.1348046 
... Procrustes: rmse 0.006551801  max resid 0.04608656 
Run 10 stress 0.134619 
... Procrustes: rmse 0.006841563  max resid 0.04571578 
Run 11 stress 0.1348237 
... Procrustes: rmse 0.006648345  max resid 0.04605669 
Run 12 stress 0.1344331 
... Procrustes: rmse 0.00006317981  max resid 0.0001865383 
... Similar to previous best
Run 13 stress 0.1405637 
Run 14 stress 0.1346406 
... Procrustes: rmse 0.007270798  max resid 0.04575267 
Run 15 stress 0.1405831 
Run 16 stress 0.1406945 
Run 17 stress 0.1346406 
... Procrustes: rmse 0.007285202  max resid 0.04573278 
Run 18 stress 0.1344509 
... Procrustes: rmse 0.001186371  max resid 0.006935107 
... Similar to previous best
Run 19 stress 0.1405634 
Run 20 stress 0.1344331 
... Procrustes: rmse 0.00007290489  max resid 0.0002305292 
... Similar to previous best
*** Best solution repeated 5 times
neotropic_cities_nmds
scree_plot(neotropic_cities_nmds)

neotropic_cities = cluster_cities(city_nmds = neotropic_cities_nmds, cities_community_data = neotropic_cities_community_data, centers = 5)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(neotropic_cities)

neotropic_biomes = resolve[resolve$REALM == 'Neotropic',c('REALM')]
 
ggplot() + 
  geom_sf(data = neotropic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = neotropic_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_clusters.jpg'))
Saving 7.29 x 4.51 in image

test_value('Neotropic MNTD', neotropic_cities$mntd_normalised)
[1] "Neotropic MNTD mean 0.53 "
test_value('Neotropic beak gape', neotropic_cities$gape_width_fdiv_normalised)
[1] "Neotropic beak gape mean 0.44 "
test_value('Neotropic kipps distance', neotropic_cities$kipps_distance_fdiv_normalised)
[1] "Neotropic kipps distance mean 0.58 *"

Neotropic Cluster 1

neotropic_cluster1 = neotropic_cities %>% filter(cluster == 1)
plot_city_cluster(neotropic_cluster1, 'Neotropic cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster1.jpg'), width = get_image_width(neotropic_cluster1), height = get_image_height(neotropic_cluster1), units = "mm")

Neotropic Cluster 2

neotropic_cluster2 = neotropic_cities %>% filter(cluster == 2)
plot_city_cluster(neotropic_cluster2, 'Neotropic cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster2.jpg'), width = get_image_width(neotropic_cluster2), height = get_image_height(neotropic_cluster2), units = "mm")

Neotropic Cluster 3

neotropic_cluster3 = neotropic_cities %>% filter(cluster == 3)
plot_city_cluster(neotropic_cluster3, 'Neotropic cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster3.jpg'), width = get_image_width(neotropic_cluster3), height = get_image_height(neotropic_cluster3), units = "mm")

Neotropic Cluster 4

neotropic_cluster4 = neotropic_cities %>% filter(cluster == 4)
plot_city_cluster(neotropic_cluster4, 'Neotropic cluster 4')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster4.jpg'), width = get_image_width(neotropic_cluster4), height = get_image_height(neotropic_cluster4), units = "mm")

Neotropic Cluster 5

neotropic_cluster5 = neotropic_cities %>% filter(cluster == 5)
plot_city_cluster(neotropic_cluster5, 'Neotropic cluster 5')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster5.jpg'), width = get_image_width(neotropic_cluster5), height = get_image_height(neotropic_cluster5), units = "mm")

Palearctic

palearctic_cities_community_data = community_data_metrics %>% filter(core_realm == 'Palearctic')
palearctic_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
 [1] "Lisbon"                "Porto"                 "Marrakesh"             "Seville"               "Dublin"                "Málaga"               
 [7] "Madrid"                "Glasgow"               "Bilbao"                "Liverpool"             "Bristol"               "Manchester"           
[13] "Birmingham"            "Leeds"                 "Newcastle upon Tyne"   "Sheffield"             "Nottingham"            "Valencia"             
[19] "London"                "Toulouse"              "Paris"                 "Barcelona"             "Rotterdam [The Hague]" "Brussels"             
[25] "Amsterdam"             "Lyon"                  "Marseille"             "Dusseldorf"            "Nice"                  "Frankfurt am Main"    
[31] "Zurich"                "Oslo"                  "Stuttgart"             "Hamburg"               "Genoa"                 "Nuremberg"            
[37] "Copenhagen"            "Munich"                "Berlin"                "Dresden"               "Rome"                  "Prague"               
[43] "Stockholm"             "Poznan"                "Vienna"                "Wroclaw"               "Zagreb"                "Gdansk"               
[49] "Budapest"              "Krakow"                "Warsaw"                "Helsinki"              "Riga"                  "Belgrade"             
[55] "Lviv"                  "Sofia"                 "Thessaloniki"          "Saint Petersburg"      "Minsk"                 "Athens"               
[61] "Kyiv"                  "Istanbul"              "Odesa"                 "Samsun"                "Luxor"                 "Tel Aviv"             
[67] "Jerusalem"             "Tbilisi"               "Yerevan"               "Kuwait City"           "Doha"                  "Abu Dhabi"            
[73] "Dubai"                 "Bishkek"              

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
palearctic_cities_nmds = community_nmds(communities %>% filter(city_id %in% palearctic_cities_community_data$city_id)) 
Run 0 stress 0.04961857 
Run 1 stress 0.06522066 
Run 2 stress 0.06770805 
Run 3 stress 0.06505779 
Run 4 stress 0.08915955 
Run 5 stress 0.05395855 
Run 6 stress 0.04961847 
... New best solution
... Procrustes: rmse 0.0001353415  max resid 0.0006424586 
... Similar to previous best
Run 7 stress 0.07723898 
Run 8 stress 0.06982598 
Run 9 stress 0.05002297 
... Procrustes: rmse 0.008523462  max resid 0.02080155 
Run 10 stress 0.05001003 
... Procrustes: rmse 0.06742951  max resid 0.2228023 
Run 11 stress 0.05354024 
Run 12 stress 0.07364969 
Run 13 stress 0.04967779 
... Procrustes: rmse 0.02454439  max resid 0.1155996 
Run 14 stress 0.06518621 
Run 15 stress 0.0547908 
Run 16 stress 0.0675422 
Run 17 stress 0.06304729 
Run 18 stress 0.04968731 
... Procrustes: rmse 0.02483805  max resid 0.1165918 
Run 19 stress 0.06829409 
Run 20 stress 0.08070857 
*** Best solution repeated 1 times
palearctic_cities_nmds
scree_plot(palearctic_cities_nmds)

palearctic_cities = cluster_cities(city_nmds = palearctic_cities_nmds, cities_community_data = palearctic_cities_community_data, centers = 7)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(palearctic_cities)

palearctic_biomes = st_crop(resolve[resolve$REALM == 'Palearctic',c('REALM')], xmin = -30, ymin = 20, xmax = 80, ymax = 65)
although coordinates are longitude/latitude, st_intersection assumes that they are planar
Warning: attribute variables are assumed to be spatially constant throughout all geometries
 
ggplot() + 
  geom_sf(data = palearctic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = palearctic_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_clusters.jpg'))
Saving 7.29 x 4.51 in image

test_value('Palearctic MNTD', palearctic_cities$mntd_normalised)
[1] "Palearctic MNTD mean 0.58 *"
test_value('Palearctic beak gape', palearctic_cities$gape_width_fdiv_normalised)
[1] "Palearctic beak gape mean 0.86 ***"
test_value('Palearctic kipps distance', palearctic_cities$kipps_distance_fdiv_normalised)
[1] "Palearctic kipps distance mean 0.91 ***"

Palearctic Cluster 1

palearctic_cluster1 = palearctic_cities %>% filter(cluster == 1)
plot_city_cluster(palearctic_cluster1, 'Palearctic cluster 1')
Warning: cannot compute exact p-value with ties
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster1.jpg'), width = get_image_width(palearctic_cluster1), height = get_image_height(palearctic_cluster1), units = "mm")

Palearctic Cluster 2

palearctic_cluster2 = palearctic_cities %>% filter(cluster == 2)
plot_city_cluster(palearctic_cluster2, 'Palearctic cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster2.jpg'), width = get_image_width(palearctic_cluster2), height = get_image_height(palearctic_cluster2), units = "mm")

Palearctic Cluster 3

palearctic_cluster3 = palearctic_cities %>% filter(cluster == 3)
plot_city_cluster(palearctic_cluster3, 'Palearctic cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster3.jpg'), width = get_image_width(palearctic_cluster3), height = get_image_height(palearctic_cluster3), units = "mm")

Palearctic Cluster 4

palearctic_cluster4 = palearctic_cities %>% filter(cluster == 4)
plot_city_cluster(palearctic_cluster4, 'Palearctic cluster 4')
Warning: cannot compute exact p-value with tiesWarning: cannot compute exact p-value with ties
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster4.jpg'), width = get_image_width(palearctic_cluster4), height = get_image_height(palearctic_cluster4), units = "mm")

Palearctic Cluster 5

palearctic_cluster5 = palearctic_cities %>% filter(cluster == 5)
plot_city_cluster(palearctic_cluster5, 'Palearctic cluster 5')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster5.jpg'), width = get_image_width(palearctic_cluster5), height = get_image_height(palearctic_cluster5), units = "mm")

Palearctic Cluster 6

palearctic_cluster6 = palearctic_cities %>% filter(cluster == 6)
plot_city_cluster(palearctic_cluster6, 'Palearctic cluster 6')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster6.jpg'), width = get_image_width(palearctic_cluster6), height = get_image_height(palearctic_cluster6), units = "mm")

Palearctic Cluster 7

palearctic_cluster7 = palearctic_cities %>% filter(cluster == 7)
plot_city_cluster(palearctic_cluster7, 'Palearctic cluster 7')
Warning: cannot compute exact p-value with ties
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster7.jpg'), width = get_image_width(palearctic_cluster7), height = get_image_height(palearctic_cluster7), units = "mm")

Afrotropic

afrotropic_cities_community_data = community_data_metrics %>% filter(core_realm == 'Afrotropic')
afrotropic_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
[1] "Cape Town"    "Johannesburg" "Pretoria"     "Kigali"       "Kampala"      "Arusha"       "Nairobi"      "Addis Ababa"  "Antananarivo"

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
afrotropic_cities_nmds = community_nmds(communities %>% filter(city_id %in% afrotropic_cities_community_data$city_id)) 
Run 0 stress 0.00009014786 
Run 1 stress 0.0000894423 
... New best solution
... Procrustes: rmse 0.0002302813  max resid 0.0003204519 
... Similar to previous best
Run 2 stress 0.0002552434 
... Procrustes: rmse 0.001471005  max resid 0.001947622 
... Similar to previous best
Run 3 stress 0.00009602873 
... Procrustes: rmse 0.0003134658  max resid 0.0004532654 
... Similar to previous best
Run 4 stress 0.0004206102 
... Procrustes: rmse 0.002382671  max resid 0.003350133 
... Similar to previous best
Run 5 stress 0.001267824 
Run 6 stress 0.00009984846 
... Procrustes: rmse 0.000573798  max resid 0.0007724523 
... Similar to previous best
Run 7 stress 0.00009404138 
... Procrustes: rmse 0.0002198999  max resid 0.0002986639 
... Similar to previous best
Run 8 stress 0.00006937968 
... New best solution
... Procrustes: rmse 0.0001548829  max resid 0.0003491895 
... Similar to previous best
Run 9 stress 0.00009515083 
... Procrustes: rmse 0.0002518215  max resid 0.0003915592 
... Similar to previous best
Run 10 stress 0.0002810887 
... Procrustes: rmse 0.001624066  max resid 0.002239305 
... Similar to previous best
Run 11 stress 0.3209238 
Run 12 stress 0.0001607375 
... Procrustes: rmse 0.0009389978  max resid 0.001284128 
... Similar to previous best
Run 13 stress 0.00009851387 
... Procrustes: rmse 0.0002528255  max resid 0.0003960776 
... Similar to previous best
Run 14 stress 0.00009299962 
... Procrustes: rmse 0.0002384291  max resid 0.0003789721 
... Similar to previous best
Run 15 stress 0.0001360544 
... Procrustes: rmse 0.0007908028  max resid 0.001108713 
... Similar to previous best
Run 16 stress 0.00009817612 
... Procrustes: rmse 0.0002561838  max resid 0.0003936572 
... Similar to previous best
Run 17 stress 0.002120919 
Run 18 stress 0.00009438058 
... Procrustes: rmse 0.000245643  max resid 0.0003771271 
... Similar to previous best
Run 19 stress 0.00009470345 
... Procrustes: rmse 0.0002746505  max resid 0.0004407572 
... Similar to previous best
Run 20 stress 0.00008594013 
... Procrustes: rmse 0.0002324527  max resid 0.0003605282 
... Similar to previous best
*** Best solution repeated 11 times
Warning: stress is (nearly) zero: you may have insufficient data
afrotropic_cities_nmds
scree_plot(afrotropic_cities_nmds)

afrotropic_cities = cluster_cities(city_nmds = afrotropic_cities_nmds, cities_community_data = afrotropic_cities_community_data, centers = 2)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(afrotropic_cities)

afrotropic_biomes = resolve[resolve$REALM == 'Afrotropic',c('REALM')]
 
ggplot() + 
  geom_sf(data = afrotropic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = afrotropic_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'afrotropic_clusters.jpg'))
Saving 7.29 x 4.51 in image

test_value('Afrotropic MNTD', afrotropic_cities$mntd_normalised)
[1] "Afrotropic MNTD mean 0.14 *"
test_value('Afrotropic beak gape', afrotropic_cities$gape_width_fdiv_normalised)
[1] "Afrotropic beak gape mean 0.41 "
test_value('Afrotropic kipps distance', afrotropic_cities$kipps_distance_fdiv_normalised)
[1] "Afrotropic kipps distance mean 0.42 "

Afrotropic Cluster 1

afrotropic_cluster1 = afrotropic_cities %>% filter(cluster == 1)
plot_city_cluster(afrotropic_cluster1, 'Afrotropic cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'afrotropic_cluster1.jpg'), width = get_image_width(afrotropic_cluster1), height = get_image_height(afrotropic_cluster1), units = "mm")

Afrotropic Cluster 2

afrotropic_cluster2 = afrotropic_cities %>% filter(cluster == 2)
plot_city_cluster(afrotropic_cluster2, 'Afrotropic cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'afrotropic_cluster2.jpg'), width = get_image_width(afrotropic_cluster2), height = get_image_height(afrotropic_cluster2), units = "mm")

Indomalayan

indomalayan_cities_community_data = community_data_metrics %>% filter(core_realm == 'Indomalayan')
indomalayan_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
  [1] "Srinagar"            "Jamnagar"            "Jammu"               "Rajkot"              "Bikaner"             "Jodhpur"             "Jalandhar"          
  [8] "Ahmedabad"           "Bhavnagar"           "Ludhiana"            "Anand"               "Udaipur"             "Surat"               "Vadodara"           
 [15] "Ajmer"               "Chandigarh"          "Vasai-Virar"         "Mumbai"              "Jaipur"              "Delhi [New Delhi]"   "Nashik"             
 [22] "Dehradun"            "Kota"                "Pune"                "Haridwar"            "Dhule"               "Ujjain"              "Indore"             
 [29] "Ahmadnagar"          "Kolhapur"            "Jalgaon"             "Agra"                "Aurangabad"          "Sangli"              "Belagavi"           
 [36] "Gwalior"             "Budaun"              "Bareilly"            "Dharwad"             "Bhopal"              "Bhind"               "Mangaluru"          
 [43] "Solapur"             "Vijayapura"          "Akola"               "Latur"               "Kannur"              "Davanagere"          "Thalassery"         
 [50] "Amravati"            "Kalaburagi"          "Kozhikode"           "Guruvayur"           "Malappuram"          "Lucknow"             "Thrissur"           
 [57] "Mysuru"              "Kochi"               "Alappuzha"           "Nagpur"              "Kollam"              "Jabalpur"            "Ettumanoor"         
 [64] "Hyderabad"           "Coimbatore"          "Bengaluru"           "Thiruvananthapuram"  "Tiruppur"            "Faizabad"            "Erode"              
 [71] "Prayagraj"           "Pratapgarh"          "Salem"               "Dindigul"            "Madurai"             "Tiruchirappalli"     "Durg"               
 [78] "Vellore"             "Tirupati"            "Raipur"              "Bilaspur"            "Vijayawada"          "Puducherry"          "Chennai"            
 [85] "Kathmandu"           "Colombo"             "Rajamahendravaram"   "Patna"               "Kandy"               "Bihar Sharif"        "Visakhapatnam"      
 [92] "Ranchi"              "Brahmapur"           "Jamshedpur"          "Darjeeling"          "Siliguri"            "Cuttack"             "Bhubaneshwar"       
 [99] "Jalpaiguri"          "Berhampore"          "Kolkata"             "Krishnanagar"        "Guwahati [Dispur]"   "Agartala"            "Silchar"            
[106] "Dimapur"             "Bangkok"             "George Town"         "Kuala Lumpur"        "Phnom Penh"          "Singapore"           "Hong Kong"          
[113] "Sha Tin"             "Hsinchu"             "Taichung"            "New Taipei [Taipei]" "Tainan"              "Denpasar"            "Kaohsiung"          
[120] "Kota Kinabalu"      

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
indomalayan_cities_nmds = community_nmds(communities %>% filter(city_id %in% indomalayan_cities_community_data$city_id)) 
Run 0 stress 0.1190668 
Run 1 stress 0.1224401 
Run 2 stress 0.1394961 
Run 3 stress 0.1161598 
... New best solution
... Procrustes: rmse 0.02798297  max resid 0.2346031 
Run 4 stress 0.1540252 
Run 5 stress 0.1175538 
Run 6 stress 0.1167657 
Run 7 stress 0.1384412 
Run 8 stress 0.1241275 
Run 9 stress 0.1163498 
... Procrustes: rmse 0.025336  max resid 0.2481842 
Run 10 stress 0.1153501 
... New best solution
... Procrustes: rmse 0.008287738  max resid 0.08217213 
Run 11 stress 0.1199235 
Run 12 stress 0.1172201 
Run 13 stress 0.151756 
Run 14 stress 0.1246307 
Run 15 stress 0.134023 
Run 16 stress 0.1580175 
Run 17 stress 0.1229198 
Run 18 stress 0.1502633 
Run 19 stress 0.1189366 
Run 20 stress 0.117072 
Run 21 stress 0.1458634 
Run 22 stress 0.137476 
Run 23 stress 0.1186188 
Run 24 stress 0.1637221 
Run 25 stress 0.1191928 
Run 26 stress 0.1164559 
Run 27 stress 0.142366 
Run 28 stress 0.1379337 
Run 29 stress 0.1191853 
Run 30 stress 0.1266232 
*** Best solution was not repeated -- monoMDS stopping criteria:
    29: stress ratio > sratmax
     1: scale factor of the gradient < sfgrmin
indomalayan_cities_nmds
scree_plot(indomalayan_cities_nmds)

indomalayan_cities = cluster_cities(city_nmds = indomalayan_cities_nmds, cities_community_data = indomalayan_cities_community_data, centers = 5)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(indomalayan_cities)

indomalayan_biomes = resolve[resolve$REALM == 'Indomalayan',c('REALM')]
 
ggplot() + 
  geom_sf(data = indomalayan_biomes, aes(geometry = geometry)) + 
  geom_sf(data = indomalayan_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_clusters.jpg'))
Saving 7.29 x 4.51 in image

test_value('Indomalayan MNTD', indomalayan_cities$mntd_normalised)
[1] "Indomalayan MNTD mean 0.44 ***"
test_value('Indomalayan beak gape', indomalayan_cities$gape_width_fdiv_normalised)
[1] "Indomalayan beak gape mean 0.52 "
test_value('Indomalayan kipps distance', indomalayan_cities$kipps_distance_fdiv_normalised)
[1] "Indomalayan kipps distance mean 0.82 ***"

Indomalayan Cluster 1

indomalayan_cluster1 = indomalayan_cities %>% filter(cluster == 1)
plot_city_cluster(indomalayan_cluster1, 'Indomalayan cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster1.jpg'), width = get_image_width(indomalayan_cluster1), height = get_image_height(indomalayan_cluster1), units = "mm")

Indomalayan Cluster 2

indomalayan_cluster2 = indomalayan_cities %>% filter(cluster == 2)
plot_city_cluster(indomalayan_cluster2, 'Indomalayan cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster2.jpg'), width = get_image_width(indomalayan_cluster2), height = get_image_height(indomalayan_cluster2), units = "mm")

Indomalayan Cluster 3

indomalayan_cluster3 = indomalayan_cities %>% filter(cluster == 3)
plot_city_cluster(indomalayan_cluster3, 'Indomalayan cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster3.jpg'), width = get_image_width(indomalayan_cluster3), height = get_image_height(indomalayan_cluster3), units = "mm")

Indomalayan Cluster 4

indomalayan_cluster4 = indomalayan_cities %>% filter(cluster == 4)
plot_city_cluster(indomalayan_cluster4, 'Indomalayan cluster 4')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster4.jpg'), width = get_image_width(indomalayan_cluster4), height = get_image_height(indomalayan_cluster4), units = "mm")

Indomalayan Cluster 5

indomalayan_cluster5 = indomalayan_cities %>% filter(cluster == 5)
plot_city_cluster(indomalayan_cluster5, 'Indomalayan cluster 5')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster5.jpg'), width = get_image_width(indomalayan_cluster5), height = get_image_height(indomalayan_cluster5), units = "mm")

LS0tCnRpdGxlOiAiRGVlcCBkaXZlIHJlZ2lvbmFsIGNvbW11bml0aWVzIC0gdXNpbmcgcmVsYXRpdmUgYWJ1bmRhbmNlIHByb3giCm91dHB1dDogaHRtbF9ub3RlYm9vawpiaWJsaW9ncmFwaHk6IC4uL3JlZi5iaWIgIAotLS0KCmBgYHtyfQpzb3VyY2UoJy4uL2Vudi5SJykKYGBgCgojIFNwZWNpZXMgaW4gY29tbXVuaXRpZXMKSXQgc2VlbXMgcmVhc29uYWJsZSB0byBleHBlY3QgdGhhdCBjaXRpZXMgd2l0aCBzaW1pYWxyIHJlZ2lvbmFsIHBvb2xzIHdpbGwgaGF2ZSBzaW1pbGFyIHNwZWNpZXMgZW50ZXJpbmcgdGhlIGNpdHksIGFuZCB0aHVzIGEgc2ltaWxhciByZXNwb25zZSB0byB1cmJhbmlzYXRpb24uCgojIyBMb2FkIGRhdGEKYGBge3J9CmNpdHlfZWZmb3J0ID0gcmVhZF9jc3YoZmlsZW5hbWUoQ0lUWV9EQVRBX09VVFBVVF9ESVIsICdjaXR5X2VmZm9ydC5jc3YnKSkKY2l0eV9lZmZvcnQKYGBgCgpgYGB7cn0KY29tbXVuaXRpZXMgPSByZWFkX2NzdihmaWxlbmFtZShDT01NVU5JVFlfT1VUUFVUX0RJUiwgJ2NvbW11bml0aWVzX2Zvcl9hbmFseXNpcy5jc3YnKSkKCmNvbW11bml0aWVzX3N1bW1hcnkgPSBjb21tdW5pdGllcyAlPiUgZ3JvdXBfYnkoY2l0eV9pZCkgJT4lIHN1bW1hcmlzZSgKICByZWdpb25hbF9wb29sX3NpemUgPSBuKCksIAogIHVyYmFuX3Bvb2xfc2l6ZSA9IHN1bShyZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHkgPiAwKQopICU+JSBsZWZ0X2pvaW4oY2l0eV9lZmZvcnQgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgcGVyY2VudGFnZV90b3RhbF9jaXR5X2FyZWFfc3VydmV5ZWQpKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY29tbXVuaXRpZXMgJT4lIGZpbHRlcihyZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHkgPiAwKSwgYWVzKHggPSByZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHkpKSArIGdlb21fYmFyKHN0YXQgPSAiYmluIikKYGBgCgpgYGB7cn0KY2l0eV9wb2ludHMgPSBzdF9jZW50cm9pZChyZWFkX3NmKGZpbGVuYW1lKENJVFlfREFUQV9PVVRQVVRfRElSLCAnY2l0eV9zZWxlY3Rpb24uc2hwJykpKQpgYGAKCmBgYHtyfQpjb21tdW5pdHlfZGF0YV9tZXRyaWNzID0gcmVhZF9jc3YoZmlsZW5hbWUoQ09NTVVOSVRZX09VVFBVVF9ESVIsICdjb21tdW5pdHlfYXNzZW1ibHlfbWV0cmljc191c2luZ19yZWxhdGl2ZV9hYnVuZGFuY2UuY3N2JykpICU+JQogIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgbW50ZF9ub3JtYWxpc2VkLCBmZGl2X25vcm1hbGlzZWQsIG1hc3NfZmRpdl9ub3JtYWxpc2VkLCBraXBwc19kaXN0YW5jZV9mZGl2X25vcm1hbGlzZWQsIHRyb3BoaWNfdHJhaXRfZmRpdl9ub3JtYWxpc2VkLCBnYXBlX3dpZHRoX2ZkaXZfbm9ybWFsaXNlZCkgJT4lCiAgbGVmdF9qb2luKHJlYWRfY3N2KGZpbGVuYW1lKENJVFlfREFUQV9PVVRQVVRfRElSLCAncmVhbG1zLmNzdicpKSkgJT4lCiAgbGVmdF9qb2luKGNvbW11bml0aWVzX3N1bW1hcnkpICU+JQogIGxlZnRfam9pbihjaXR5X3BvaW50c1ssYygnY2l0eV9pZCcsICdjaXR5X25tJyldKSAlPiUKICByZW5hbWUoY2l0eV9uYW1lPSdjaXR5X25tJykgJT4lCiAgbmEub21pdCgpICU+JQogIGFycmFuZ2UoY2l0eV9pZCkKCmNvbW11bml0eV9kYXRhX21ldHJpY3MKYGBgCgpgYGB7cn0KdGVzdF92YWx1ZSA9IGZ1bmN0aW9uKG5hbWUsIG5vcm1hbGlzZWRfbGlzdCkgewogIHdpbGNveF90ZXN0X3Jlc3VsdCA9IHdpbGNveC50ZXN0KG5vcm1hbGlzZWRfbGlzdCwgbXUgPSAwLjUpCiAgCiAgc2lnbmlmaWNhbmNlID0gaWZlbHNlKHdpbGNveF90ZXN0X3Jlc3VsdCRwLnZhbHVlIDwgMC4wMDAxLCAnKioqJywgCiAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSh3aWxjb3hfdGVzdF9yZXN1bHQkcC52YWx1ZSA8IDAuMDAxLCAnKionLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSh3aWxjb3hfdGVzdF9yZXN1bHQkcC52YWx1ZSA8IDAuMDEsICcqJywgJycpKSkKICBtID0gbWVhbihub3JtYWxpc2VkX2xpc3QpCiAgCiAgcGFzdGUobmFtZSwgJ21lYW4nLCByb3VuZChtLCAyKSwgc2lnbmlmaWNhbmNlKQp9CmBgYAoKYGBge3J9CnRlc3RfdmFsdWUoJ0dsb2JhbCBNTlREJywgY29tbXVuaXR5X2RhdGFfbWV0cmljcyRtbnRkX25vcm1hbGlzZWQpCnRlc3RfdmFsdWUoJ0dsb2JhbCBiZWFrIGdhcGUnLCBjb21tdW5pdHlfZGF0YV9tZXRyaWNzJGdhcGVfd2lkdGhfZmRpdl9ub3JtYWxpc2VkKQp0ZXN0X3ZhbHVlKCdHbG9iYWwga2lwcHMgZGlzdGFuY2UnLCBjb21tdW5pdHlfZGF0YV9tZXRyaWNzJGtpcHBzX2Rpc3RhbmNlX2ZkaXZfbm9ybWFsaXNlZCkKYGBgCgpMb2FkIHRyYWl0IGRhdGEKYGBge3J9CnRyYWl0cyA9IHJlYWRfY3N2KGZpbGVuYW1lKFRBWE9OT01ZX09VVFBVVF9ESVIsICd0cmFpdHNfamV0ei5jc3YnKSkKaGVhZCh0cmFpdHMpCmBgYAoKYGBge3J9CmZldGNoX25vcm1hbGlzZWRfdHJhaXRzID0gZnVuY3Rpb24ocmVxdWlyZWRfc3BlY2llc19saXN0KSB7CiAgcmVxdWlyZWRfdHJhaXRzID0gdHJhaXRzICU+JSBmaWx0ZXIoamV0el9zcGVjaWVzX25hbWUgJWluJSByZXF1aXJlZF9zcGVjaWVzX2xpc3QpCiAgCiAgcmVxdWlyZWRfdHJhaXRzJGdhcGVfd2lkdGhfbm9ybWFsaXNlZCA9IG5vcm1hbGlzZShyZXF1aXJlZF90cmFpdHMkZ2FwZV93aWR0aCwgbWluKHJlcXVpcmVkX3RyYWl0cyRnYXBlX3dpZHRoKSwgbWF4KHJlcXVpcmVkX3RyYWl0cyRnYXBlX3dpZHRoKSkKICByZXF1aXJlZF90cmFpdHMka2lwcHNfZGlzdGFuY2VfZmRpdl9ub3JtYWxpc2VkID0gbm9ybWFsaXNlKHJlcXVpcmVkX3RyYWl0cyRraXBwc19kaXN0YW5jZSwgbWluKHJlcXVpcmVkX3RyYWl0cyRraXBwc19kaXN0YW5jZSksIG1heChyZXF1aXJlZF90cmFpdHMka2lwcHNfZGlzdGFuY2UpKQogIHJlcXVpcmVkX3RyYWl0cyRtYXNzX25vcm1hbGlzZWQgPSBub3JtYWxpc2UocmVxdWlyZWRfdHJhaXRzJG1hc3MsIG1pbihyZXF1aXJlZF90cmFpdHMkbWFzcyksIG1heChyZXF1aXJlZF90cmFpdHMkbWFzcykpCiAgCiAgdHJhaXRzX25vcm1hbGlzZWRfbG9uZyA9IHJlcXVpcmVkX3RyYWl0cyAlPiUgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKCdnYXBlX3dpZHRoX25vcm1hbGlzZWQnLCAna2lwcHNfZGlzdGFuY2VfZmRpdl9ub3JtYWxpc2VkJywgJ21hc3Nfbm9ybWFsaXNlZCcpLCBuYW1lc190byA9ICd0cmFpdCcsIHZhbHVlc190byA9ICdub3JtYWxpc2VkX3ZhbHVlJykgJT4lIGRwbHlyOjpzZWxlY3QoamV0el9zcGVjaWVzX25hbWUsIHRyYWl0LCBub3JtYWxpc2VkX3ZhbHVlKQogIHRyYWl0c19ub3JtYWxpc2VkX2xvbmckdHJhaXQgPSBmYWN0b3IodHJhaXRzX25vcm1hbGlzZWRfbG9uZyR0cmFpdCwgbGV2ZWxzID0gYygnZ2FwZV93aWR0aF9ub3JtYWxpc2VkJywgJ2tpcHBzX2Rpc3RhbmNlX2ZkaXZfbm9ybWFsaXNlZCcsICdtYXNzX25vcm1hbGlzZWQnKSwgbGFiZWxzID0gYygnR2FwZSBXaWR0aCcsICdLaXBwcyBEaXN0YW5jZScsICdNYXNzJykpCiAgCiAgdHJhaXRzX25vcm1hbGlzZWRfbG9uZwp9CgpmZXRjaF9ub3JtYWxpc2VkX3RyYWl0cyhjKCdBcGxvcGVsaWFfbGFydmF0YScsICdDaGFsY29waGFwc19pbmRpY2EnLCAnQ2Fsb2VuYXNfbmljb2JhcmljYScpKQpgYGAKCgpSZWFkIGluIG91ciBwaHlsb2dlbnkKYGBge3J9CnBoeWxvX3RyZWUgPSByZWFkLnRyZWUoZmlsZW5hbWUoVEFYT05PTVlfT1VUUFVUX0RJUiwgJ3BoeWxvZ2VueS50cmUnKSkKZ2d0cmVlKHBoeWxvX3RyZWUsIGxheW91dD0nY2lyY3VsYXInKQpgYGAKCkxvYWQgcmVzb2x2ZSBlY29yZWdpb25zCmBgYHtyfQpyZXNvbHZlID0gcmVhZF9yZXNvbHZlKCkKYGBgCgojIyBDcmVhdGUgaGVscGVyIGZ1bmN0aW9ucwpgYGB7cn0KdG9fc3BlY2llc19tYXRyaXggPSBmdW5jdGlvbihmaWx0ZXJlZF9jb21tdW5pdGllcykgewogIGZpbHRlcmVkX2NvbW11bml0aWVzICU+JSAKICAgIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgamV0el9zcGVjaWVzX25hbWUpICU+JSAKICAgIGRpc3RpbmN0KCkgJT4lCiAgICBtdXRhdGUocHJlc2VudCA9IFRSVUUpICU+JSAKICAgIHBpdm90X3dpZGVyKAogICAgICBuYW1lc19mcm9tID0gamV0el9zcGVjaWVzX25hbWUsIAogICAgICB2YWx1ZXNfZnJvbSA9ICJwcmVzZW50IiwgCiAgICAgIHZhbHVlc19maWxsID0gbGlzdChwcmVzZW50ID0gRikKICAgICkgJT4lIAogICAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXModmFyPSdjaXR5X2lkJykKfQpgYGAKCmBgYHtyfQpjb21tdW5pdHlfbm1kcyA9IGZ1bmN0aW9uKGZpbHRlcmVkX2NvbW11bml0aWVzKSB7CiAgc3BlY2llc19tYXRyaXggPSB0b19zcGVjaWVzX21hdHJpeChmaWx0ZXJlZF9jb21tdW5pdGllcykKICBubWRzIDwtIG1ldGFNRFMoc3BlY2llc19tYXRyaXgsIGs9MiwgdHJ5bWF4ID0gMzApCiAgbm1kc19yZXN1bHQgPSBkYXRhLmZyYW1lKHZlZ2FuOjpzY29yZXMobm1kcykkc2l0ZXMpCiAgbm1kc19yZXN1bHQkY2l0eV9pZCA9IGFzLmRvdWJsZShyb3duYW1lcyhubWRzX3Jlc3VsdCkpCiAgcm93bmFtZXMobm1kc19yZXN1bHQpID0gTlVMTAogIG5tZHNfcmVzdWx0Cn0KYGBgCgpodHRwczovL3d3dy5kYXRhY2FtcC5jb20vdHV0b3JpYWwvay1tZWFucy1jbHVzdGVyaW5nLXIKYGBge3J9CnNjcmVlX3Bsb3QgPSBmdW5jdGlvbihjb21tdW5pdHlfbm1kc19kYXRhKSB7CiAgIyBEZWNpZGUgaG93IG1hbnkgY2x1c3RlcnMgdG8gbG9vayBhdAogIG5fY2x1c3RlcnMgPC0gbWluKDEwLCBucm93KGNvbW11bml0eV9ubWRzX2RhdGEpIC0gMSkKICAKICAjIEluaXRpYWxpemUgdG90YWwgd2l0aGluIHN1bSBvZiBzcXVhcmVzIGVycm9yOiB3c3MKICB3c3MgPC0gbnVtZXJpYyhuX2NsdXN0ZXJzKQogIAogIHNldC5zZWVkKDEyMykKICAKICAjIExvb2sgb3ZlciAxIHRvIG4gcG9zc2libGUgY2x1c3RlcnMKICBmb3IgKGkgaW4gMTpuX2NsdXN0ZXJzKSB7CiAgICAjIEZpdCB0aGUgbW9kZWw6IGttLm91dAogICAga20ub3V0IDwtIGttZWFucyhjb21tdW5pdHlfbm1kc19kYXRhWyxjKCdOTURTMScsJ05NRFMyJyldLCBjZW50ZXJzID0gaSwgbnN0YXJ0ID0gMjApCiAgICAjIFNhdmUgdGhlIHdpdGhpbiBjbHVzdGVyIHN1bSBvZiBzcXVhcmVzCiAgICB3c3NbaV0gPC0ga20ub3V0JHRvdC53aXRoaW5zcwogIH0KICAKICAjIFByb2R1Y2UgYSBzY3JlZSBwbG90CiAgd3NzX2RmIDwtIHRpYmJsZShjbHVzdGVycyA9IDE6bl9jbHVzdGVycywgd3NzID0gd3NzKQogICAKICBzY3JlZV9wbG90IDwtIGdncGxvdCh3c3NfZGYsIGFlcyh4ID0gY2x1c3RlcnMsIHkgPSB3c3MsIGdyb3VwID0gMSkpICsKICAgICAgZ2VvbV9wb2ludChzaXplID0gNCkgKwogICAgICBnZW9tX2xpbmUoKSArCiAgICAgIGdlb21faGxpbmUobGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yID0gIm9yYW5nZSIsIHlpbnRlcmNlcHQgPSB3c3MpICsKICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGMoMiwgNCwgNiwgOCwgMTApKSArCiAgICAgIHhsYWIoJ051bWJlciBvZiBjbHVzdGVycycpCiAgc2NyZWVfcGxvdAp9CmBgYAoKYGBge3J9CmNsdXN0ZXJfY2l0aWVzID0gZnVuY3Rpb24oY2l0eV9ubWRzLCBjaXRpZXNfY29tbXVuaXR5X2RhdGEsIGNlbnRlcnMpIHsKICBzZXQuc2VlZCgxMjMpCiAga21lYW5zX2NsdXN0ZXJzIDwtIGttZWFucyhjaXR5X25tZHNbLGMoJ05NRFMxJywgJ05NRFMyJyldLCBjZW50ZXJzID0gY2VudGVycywgbnN0YXJ0ID0gMjApCiAgY2l0eV9ubWRzJGNsdXN0ZXIgPSBrbWVhbnNfY2x1c3RlcnMkY2x1c3RlcgogIGNpdGllc19jb21tdW5pdHlfZGF0YSAlPiUgbGVmdF9qb2luKGNpdHlfbm1kcykgJT4lIG11dGF0ZShjbHVzdGVyID0gYXMuZmFjdG9yKGNsdXN0ZXIpKQp9CmBgYAoKYGBge3J9CnBsb3Rfbm1kc19jbHVzdGVycyA9IGZ1bmN0aW9uKGNsdXN0ZXJfY2l0aWVzKSB7CiAgY2x1c3Rlcl9jaXRpZXMgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgY2l0eV9uYW1lLCBOTURTMSwgTk1EUzIsIGNsdXN0ZXIpICU+JSBkaXN0aW5jdCgpICU+JQogIGdncGxvdChhZXMoeCA9IE5NRFMxLCB5ID0gTk1EUzIsIGNvbG91ciA9IGNsdXN0ZXIpKSArIGdlb21fcG9pbnQoKSArIGdlb21fbGFiZWxfcmVwZWwoYWVzKGxhYmVsID0gY2l0eV9uYW1lKSkKfQpgYGAKCmBgYHtyfQpnZXRfcHJlc2VuY2VfY2VsbF93aWR0aCA9IGZ1bmN0aW9uKGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MpIHsKICAxMCAqIGxlbmd0aCh1bmlxdWUoY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcyRjaXR5X2lkKSkKfQoKZ2V0X3ByZXNlbmNlX2NlbGxfaGVpZ2h0ID0gZnVuY3Rpb24oY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcykgewogIHNwZWNpZXMgPSBzcGVjaWVzX2luX2NsdXN0ZXIgPSBjb21tdW5pdGllcyAlPiUgCiAgICBmaWx0ZXIoY2l0eV9pZCAlaW4lIGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MkY2l0eV9pZCkgJT4lIAogICAgZHBseXI6OnNlbGVjdChqZXR6X3NwZWNpZXNfbmFtZSkgJT4lIAogICAgZGlzdGluY3QoKQogIAogIDEwICogbnJvdyhzcGVjaWVzKQp9CgpjaXR5X21ldHJpY19oZWlnaHQgPSAzMAp0cmFpdHNfd2lkdGggPSA1MApwaHlsb190cmVlX3dpZHRoID0gMTI1CnRpdGxlX2hlaWdodCA9IDgKCmdldF9pbWFnZV9oZWlnaHQgPSBmdW5jdGlvbihjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzKSB7CiAgZ2V0X3ByZXNlbmNlX2NlbGxfaGVpZ2h0KGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MpICsgKDMgKiBjaXR5X21ldHJpY19oZWlnaHQpICsgdGl0bGVfaGVpZ2h0Cn0KCmdldF9pbWFnZV93aWR0aCA9IGZ1bmN0aW9uKGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MpIHsKICBnZXRfcHJlc2VuY2VfY2VsbF93aWR0aChjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzKSArIHRyYWl0c193aWR0aCArIHBoeWxvX3RyZWVfd2lkdGgKfQpgYGAKCmBgYHtyfQpzcGVjaWVzX2luX2NpdHlfbGFiZWwgPSBmdW5jdGlvbihzcGVjaWVzKSB7CiAgcGFzdGUoCiAgICBpZmVsc2Uoc3BlY2llcyRzZWFzb25hbCA9PSAnUmVzaWRlbnQnLCAnJywgc3Vic3RyaW5nKHNwZWNpZXMkc2Vhc29uYWwsIDAsIDEpKSwKICAgIGlmZWxzZShzcGVjaWVzJG9yaWdpbiA9PSAnTmF0aXZlJywgJycsIHN1YnN0cmluZyhzcGVjaWVzJG9yaWdpbiwgMCwgMSkpLAogICAgaWZlbHNlKHNwZWNpZXMkZGlzdGFuY2VfdG9fbm9ydGhlcm5fZWRnZV9rbSA+IDIwMCwgJycsIHBhc3RlKCdOUkwnLCByb3VuZChzcGVjaWVzJGRpc3RhbmNlX3RvX25vcnRoZXJuX2VkZ2Vfa20pLCBzZXAgPSAnICcpKSwKICAgIGlmZWxzZShzcGVjaWVzJGRpc3RhbmNlX3RvX3NvdXRoZXJuX2VkZ2Vfa20gPiAyMDAsICcnLCBwYXN0ZSgnU1JMJywgcm91bmQoc3BlY2llcyRkaXN0YW5jZV90b19ub3J0aGVybl9lZGdlX2ttKSwgc2VwID0gJyAnKSksCiAgICBzZXAgPSAnXG4nCiAgKQp9CgpzcGVjaWVzX2luX2NpdHlfbGFiZWwoaGVhZChjb21tdW5pdGllcykpCmBgYAoKYGBge3J9CnBsb3RfY2l0eV9jbHVzdGVyID0gZnVuY3Rpb24oY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcywgdGl0bGUpIHsKICBzcGVjaWVzX2luX2NsdXN0ZXIgPSBjb21tdW5pdGllcyAlPiUgCiAgICBmaWx0ZXIoY2l0eV9pZCAlaW4lIGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MkY2l0eV9pZCkgJT4lIAogICAgZHBseXI6OnNlbGVjdChqZXR6X3NwZWNpZXNfbmFtZSwgY2l0eV9uYW1lLCByZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHksIHNlYXNvbmFsLCBvcmlnaW4sIGRpc3RhbmNlX3RvX25vcnRoZXJuX2VkZ2Vfa20sIGRpc3RhbmNlX3RvX3NvdXRoZXJuX2VkZ2Vfa20pCiAgCiAgc3BlY2llc19pbl9jbHVzdGVyJGxhYmVsID0gc3BlY2llc19pbl9jaXR5X2xhYmVsKHNwZWNpZXNfaW5fY2x1c3RlcikKICB0cmVlX2Nyb3BwZWQgPC0gbGFkZGVyaXplKGRyb3AudGlwKHBoeWxvX3RyZWUsIHNldGRpZmYocGh5bG9fdHJlZSR0aXAubGFiZWwsIHNwZWNpZXNfaW5fY2x1c3RlciRqZXR6X3NwZWNpZXNfbmFtZSkpKQogICAgCiAgZ2dfdHJlZSA9IGdndHJlZSh0cmVlX2Nyb3BwZWQpCiAgCiAgZ2dfcHJlc2VuY2UgPSBnZ3Bsb3Qoc3BlY2llc19pbl9jbHVzdGVyLCBhZXMoeD1jaXR5X25hbWUsIHk9amV0el9zcGVjaWVzX25hbWUpKSArIAogICAgICAgICAgZ2VvbV90aWxlKGFlcyhmaWxsPXJlbGF0aXZlX2FidW5kYW5jZV9wcm94eSkpICsgCiAgICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsPWxhYmVsKSwgc2l6ZT0wLjc1KSArCiAgICAgICAgICBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvdXJzPWMoIiM5OEZCOTgiLCAiI0ZGRkZFMCIsICJ5ZWxsb3ciLCAib3JhbmdlIiwgIiNGRjQ1MDAiLCAicmVkIiwgInJlZCIpLCB2YWx1ZXM9YygwLCAwLjAwMDAwMDAwMDAxLCAwLjEsIDAuMjUsIDAuNSwgMC43NSwgMSksIG5hLnZhbHVlID0gInRyYW5zcGFyZW50IikgKwogICAgICAgICAgdGhlbWVfbWluaW1hbCgpICsgeGxhYihOVUxMKSArIHlsYWIoTlVMTCkgKyAKICAgICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkgKyAKICAgICAgICAgIGxhYnMoZmlsbD0nVXJiYW4gUHJveHkgQWJ1bmRhbmNlJykKICAKICBzcGVjaWVzX2luX2NsdXN0ZXJfdHJhaXRzID0gZmV0Y2hfbm9ybWFsaXNlZF90cmFpdHMoc3BlY2llc19pbl9jbHVzdGVyJGpldHpfc3BlY2llc19uYW1lKQogIAogIGdnX3RyYWl0cyA9IGdncGxvdChzcGVjaWVzX2luX2NsdXN0ZXJfdHJhaXRzLCBhZXMoeCA9IHRyYWl0LCB5ID0gamV0el9zcGVjaWVzX25hbWUsIHNpemUgPSBub3JtYWxpc2VkX3ZhbHVlKSkgKyBnZW9tX3BvaW50KCkgKyB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSksIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSkgKyB4bGFiKE5VTEwpICsgeWxhYihOVUxMKSArIGxhYnMoc2l6ZSA9ICJOb3JtYWxpc2VkIFZhbHVlIikKICAKICBnZ19jaXRpZXNfbW50ZCA9IGdncGxvdChjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzLCBhZXMoeCA9IGNpdHlfbmFtZSwgeSA9IG1udGRfbm9ybWFsaXNlZCkpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgdGhlbWVfbWluaW1hbCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCkpICsgeGxhYihOVUxMKSArIHlsYWIoIk1OVEQiKSArIHlsaW0oMCwgMSkKICAKICBnZ19jaXRpZXNfZ2FwZV9mZCA9IGdncGxvdChjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzLCBhZXMoeCA9IGNpdHlfbmFtZSwgeSA9IGdhcGVfd2lkdGhfZmRpdl9ub3JtYWxpc2VkKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSkgKyB4bGFiKE5VTEwpICsgeWxhYigiR2FwZSIpICsgeWxpbSgwLCAxKQogIAogIGdnX2NpdGllc19sb2NvX2ZkID0gZ2dwbG90KGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MsIGFlcyh4ID0gY2l0eV9uYW1lLCB5ID0ga2lwcHNfZGlzdGFuY2VfZmRpdl9ub3JtYWxpc2VkKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSkgKyB4bGFiKE5VTEwpICsgeWxhYigiS2lwcHMiKSArIHlsaW0oMCwgMSkKICAKICBnZ190aXRsZSA9IGdncGxvdCgpICsgbGFicyh0aXRsZSA9IHRpdGxlLCBzdWJ0aXRsZSA9IHBhc3RlKAogICAgdGVzdF92YWx1ZSgnTU5URCcsIGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MkbW50ZF9ub3JtYWxpc2VkKSwKICAgIHRlc3RfdmFsdWUoJ0ZEaXYnLCBjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzJGZkaXZfbm9ybWFsaXNlZCksCiAgICB0ZXN0X3ZhbHVlKCdLaXBwcyBkaXN0YW5jZScsIGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3Mka2lwcHNfZGlzdGFuY2VfZmRpdl9ub3JtYWxpc2VkKSwKICAgIHRlc3RfdmFsdWUoJ0dhcGUgd2lkdGgnLCBjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzJGdhcGVfd2lkdGhfZmRpdl9ub3JtYWxpc2VkKSwKICAgIHNlcCA9ICdcbicKICApKSArIHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHBsb3Quc3VidGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OCwgaGp1c3Q9MCwgY29sb3I9IiM0NDQ0NDQiKSkKICAKICBnZ19wcmVzZW5jZV9oZWlnaHQgPSBnZXRfcHJlc2VuY2VfY2VsbF9oZWlnaHQoY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcykKICBnZ19wcmVzZW5jZV93aWR0aCA9IGdldF9wcmVzZW5jZV9jZWxsX3dpZHRoKGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MpCiAgCiAgZ2dfcHJlc2VuY2UgJT4lIGluc2VydF90b3AoZ2dfY2l0aWVzX2xvY29fZmQsIGhlaWdodCA9IChjaXR5X21ldHJpY19oZWlnaHQgLyBnZ19wcmVzZW5jZV9oZWlnaHQpKSAlPiUgaW5zZXJ0X3RvcChnZ19jaXRpZXNfZ2FwZV9mZCwgaGVpZ2h0ID0gKGNpdHlfbWV0cmljX2hlaWdodCAvIGdnX3ByZXNlbmNlX2hlaWdodCkpICU+JSBpbnNlcnRfdG9wKGdnX2NpdGllc19tbnRkLCBoZWlnaHQgPSAoY2l0eV9tZXRyaWNfaGVpZ2h0IC8gZ2dfcHJlc2VuY2VfaGVpZ2h0KSkgJT4lIGluc2VydF9sZWZ0KGdnX3RyZWUsIHdpZHRoID0gKHBoeWxvX3RyZWVfd2lkdGggLyBnZ19wcmVzZW5jZV93aWR0aCkpICU+JSBpbnNlcnRfcmlnaHQoZ2dfdHJhaXRzLCB3aWR0aCA9ICh0cmFpdHNfd2lkdGggLyBnZ19wcmVzZW5jZV93aWR0aCkpICU+JSBpbnNlcnRfdG9wKGdnX3RpdGxlLCBoZWlnaHQgPSAodGl0bGVfaGVpZ2h0IC8gZ2dfcHJlc2VuY2VfaGVpZ2h0KSkKfQpgYGAKCmBgYHtyfQpSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVUID0gbWtkaXIoRklHVVJFU19PVVRQVVRfRElSLCAnYXBwZW5kaXhfcmVnaW9uYWxfZGVlcF9kaXZlX3VzaW5nX2FidW5kYW5jZScpCmBgYAoKIyMgTmVhcmN0aWMKYGBge3J9Cm5lYXJjdGljX2NpdGllc19jb21tdW5pdHlfZGF0YSA9IGNvbW11bml0eV9kYXRhX21ldHJpY3MgJT4lIGZpbHRlcihjb3JlX3JlYWxtID09ICdOZWFyY3RpYycpCm5lYXJjdGljX2NpdGllc19jb21tdW5pdHlfZGF0YSAlPiUgZHBseXI6OnNlbGVjdChjaXR5X25hbWUpICU+JSBkaXN0aW5jdCgpICU+JSBhcy5saXN0KCkKYGBgCgpgYGB7cn0KbmVhcmN0aWNfY2l0aWVzX25tZHMgPSBjb21tdW5pdHlfbm1kcyhjb21tdW5pdGllcyAlPiUgZmlsdGVyKGNpdHlfaWQgJWluJSBuZWFyY3RpY19jaXRpZXNfY29tbXVuaXR5X2RhdGEkY2l0eV9pZCkpIApuZWFyY3RpY19jaXRpZXNfbm1kcwpgYGAKCmBgYHtyfQpzY3JlZV9wbG90KG5lYXJjdGljX2NpdGllc19ubWRzKQpgYGAKCmBgYHtyfQpuZWFyY3RpY19jaXRpZXMgPSBjbHVzdGVyX2NpdGllcyhjaXR5X25tZHMgPSBuZWFyY3RpY19jaXRpZXNfbm1kcywgY2l0aWVzX2NvbW11bml0eV9kYXRhID0gbmVhcmN0aWNfY2l0aWVzX2NvbW11bml0eV9kYXRhLCBjZW50ZXJzID0gNCkKYGBgCgpgYGB7cn0KcGxvdF9ubWRzX2NsdXN0ZXJzKG5lYXJjdGljX2NpdGllcykKYGBgCgpgYGB7cn0KbmVhcmN0aWNfYmlvbWVzID0gc3RfY3JvcChyZXNvbHZlW3Jlc29sdmUkUkVBTE0gPT0gJ05lYXJjdGljJyxjKCdSRUFMTScpXSwgeG1pbiA9IC0yMjAsIHltaW4gPSAwLCB4bWF4ID0gMCwgeW1heCA9IDcwKQogCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gbmVhcmN0aWNfYmlvbWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSkpICsgCiAgZ2VvbV9zZihkYXRhID0gbmVhcmN0aWNfY2l0aWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSwgY29sb3IgPSBjbHVzdGVyKSkKCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnbmVhcnRpY19jbHVzdGVycy5qcGcnKSkKYGBgCmBgYHtyfQp0ZXN0X3ZhbHVlKCdOZWFyY3RpYyBNTlREJywgbmVhcmN0aWNfY2l0aWVzJG1udGRfbm9ybWFsaXNlZCkKdGVzdF92YWx1ZSgnTmVhcmN0aWMgYmVhayBnYXBlJywgbmVhcmN0aWNfY2l0aWVzJGdhcGVfd2lkdGhfZmRpdl9ub3JtYWxpc2VkKQp0ZXN0X3ZhbHVlKCdOZWFyY3RpYyBLaXBwcyBkaXN0YW5jZScsIG5lYXJjdGljX2NpdGllcyRraXBwc19kaXN0YW5jZV9mZGl2X25vcm1hbGlzZWQpCmBgYAoKCiMjIyBOZWFydGljIENsdXN0ZXIgMWAKYGBge3J9Cm5lYXJhY3RpY19jbHVzdGVyMSA9IG5lYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkKcGxvdF9jaXR5X2NsdXN0ZXIobmVhcmFjdGljX2NsdXN0ZXIxLCAnTmVhcnRpYyBjbHVzdGVyIDEnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ25lYXJ0aWNfY2x1c3RlcjEuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKG5lYXJhY3RpY19jbHVzdGVyMSksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQobmVhcmFjdGljX2NsdXN0ZXIxKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBOZWFydGljIENsdXN0ZXIgMgpgYGB7cn0KbmVhcmFjdGljX2NsdXN0ZXIyID0gbmVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKQpwbG90X2NpdHlfY2x1c3RlcihuZWFyYWN0aWNfY2x1c3RlcjIsICdOZWFydGljIGNsdXN0ZXIgMicpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnbmVhcnRpY19jbHVzdGVyMi5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgobmVhcmFjdGljX2NsdXN0ZXIyKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChuZWFyYWN0aWNfY2x1c3RlcjIpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIE5lYXJ0aWMgQ2x1c3RlciAzCmBgYHtyfQpuZWFyYWN0aWNfY2x1c3RlcjMgPSBuZWFyY3RpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDMpCnBsb3RfY2l0eV9jbHVzdGVyKG5lYXJhY3RpY19jbHVzdGVyMywgJ05lYXJ0aWMgY2x1c3RlciAzJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICduZWFydGljX2NsdXN0ZXIzLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChuZWFyYWN0aWNfY2x1c3RlcjMpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KG5lYXJhY3RpY19jbHVzdGVyMyksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgTmVhcnRpYyBDbHVzdGVyIDQKYGBge3J9Cm5lYXJhY3RpY19jbHVzdGVyNCA9IG5lYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNCkKcGxvdF9jaXR5X2NsdXN0ZXIobmVhcmFjdGljX2NsdXN0ZXI0LCAnTmVhcnRpYyBjbHVzdGVyIDQnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ25lYXJ0aWNfY2x1c3RlcjQuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKG5lYXJhY3RpY19jbHVzdGVyNCksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQobmVhcmFjdGljX2NsdXN0ZXI0KSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIE5lb3Ryb3BpYwpgYGB7cn0KbmVvdHJvcGljX2NpdGllc19jb21tdW5pdHlfZGF0YSA9IGNvbW11bml0eV9kYXRhX21ldHJpY3MgJT4lIGZpbHRlcihjb3JlX3JlYWxtID09ICdOZW90cm9waWMnKQpuZW90cm9waWNfY2l0aWVzX2NvbW11bml0eV9kYXRhICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfbmFtZSkgJT4lIGRpc3RpbmN0KCkgJT4lIGFzLmxpc3QoKQpgYGAKCmBgYHtyfQpuZW90cm9waWNfY2l0aWVzX25tZHMgPSBjb21tdW5pdHlfbm1kcyhjb21tdW5pdGllcyAlPiUgZmlsdGVyKGNpdHlfaWQgJWluJSBuZW90cm9waWNfY2l0aWVzX2NvbW11bml0eV9kYXRhJGNpdHlfaWQpKSAKbmVvdHJvcGljX2NpdGllc19ubWRzCmBgYAoKYGBge3J9CnNjcmVlX3Bsb3QobmVvdHJvcGljX2NpdGllc19ubWRzKQpgYGAKCmBgYHtyfQpuZW90cm9waWNfY2l0aWVzID0gY2x1c3Rlcl9jaXRpZXMoY2l0eV9ubWRzID0gbmVvdHJvcGljX2NpdGllc19ubWRzLCBjaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBuZW90cm9waWNfY2l0aWVzX2NvbW11bml0eV9kYXRhLCBjZW50ZXJzID0gNSkKYGBgCgpgYGB7cn0KcGxvdF9ubWRzX2NsdXN0ZXJzKG5lb3Ryb3BpY19jaXRpZXMpCmBgYAoKYGBge3J9Cm5lb3Ryb3BpY19iaW9tZXMgPSByZXNvbHZlW3Jlc29sdmUkUkVBTE0gPT0gJ05lb3Ryb3BpYycsYygnUkVBTE0nKV0KIApnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IG5lb3Ryb3BpY19iaW9tZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKyAKICBnZW9tX3NmKGRhdGEgPSBuZW90cm9waWNfY2l0aWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSwgY29sb3IgPSBjbHVzdGVyKSkKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICduZW90cm9waWNfY2x1c3RlcnMuanBnJykpCmBgYAoKYGBge3J9CnRlc3RfdmFsdWUoJ05lb3Ryb3BpYyBNTlREJywgbmVvdHJvcGljX2NpdGllcyRtbnRkX25vcm1hbGlzZWQpCnRlc3RfdmFsdWUoJ05lb3Ryb3BpYyBiZWFrIGdhcGUnLCBuZW90cm9waWNfY2l0aWVzJGdhcGVfd2lkdGhfZmRpdl9ub3JtYWxpc2VkKQp0ZXN0X3ZhbHVlKCdOZW90cm9waWMga2lwcHMgZGlzdGFuY2UnLCBuZW90cm9waWNfY2l0aWVzJGtpcHBzX2Rpc3RhbmNlX2ZkaXZfbm9ybWFsaXNlZCkKYGBgCgojIyMgTmVvdHJvcGljIENsdXN0ZXIgMQpgYGB7cn0KbmVvdHJvcGljX2NsdXN0ZXIxID0gbmVvdHJvcGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkKcGxvdF9jaXR5X2NsdXN0ZXIobmVvdHJvcGljX2NsdXN0ZXIxLCAnTmVvdHJvcGljIGNsdXN0ZXIgMScpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnbmVvdHJvcGljX2NsdXN0ZXIxLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChuZW90cm9waWNfY2x1c3RlcjEpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KG5lb3Ryb3BpY19jbHVzdGVyMSksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgTmVvdHJvcGljIENsdXN0ZXIgMgpgYGB7cn0KbmVvdHJvcGljX2NsdXN0ZXIyID0gbmVvdHJvcGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMikKcGxvdF9jaXR5X2NsdXN0ZXIobmVvdHJvcGljX2NsdXN0ZXIyLCAnTmVvdHJvcGljIGNsdXN0ZXIgMicpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnbmVvdHJvcGljX2NsdXN0ZXIyLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChuZW90cm9waWNfY2x1c3RlcjIpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KG5lb3Ryb3BpY19jbHVzdGVyMiksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgTmVvdHJvcGljIENsdXN0ZXIgMwpgYGB7cn0KbmVvdHJvcGljX2NsdXN0ZXIzID0gbmVvdHJvcGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMykKcGxvdF9jaXR5X2NsdXN0ZXIobmVvdHJvcGljX2NsdXN0ZXIzLCAnTmVvdHJvcGljIGNsdXN0ZXIgMycpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnbmVvdHJvcGljX2NsdXN0ZXIzLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChuZW90cm9waWNfY2x1c3RlcjMpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KG5lb3Ryb3BpY19jbHVzdGVyMyksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgTmVvdHJvcGljIENsdXN0ZXIgNApgYGB7cn0KbmVvdHJvcGljX2NsdXN0ZXI0ID0gbmVvdHJvcGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNCkKcGxvdF9jaXR5X2NsdXN0ZXIobmVvdHJvcGljX2NsdXN0ZXI0LCAnTmVvdHJvcGljIGNsdXN0ZXIgNCcpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnbmVvdHJvcGljX2NsdXN0ZXI0LmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChuZW90cm9waWNfY2x1c3RlcjQpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KG5lb3Ryb3BpY19jbHVzdGVyNCksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgTmVvdHJvcGljIENsdXN0ZXIgNQpgYGB7cn0KbmVvdHJvcGljX2NsdXN0ZXI1ID0gbmVvdHJvcGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNSkKcGxvdF9jaXR5X2NsdXN0ZXIobmVvdHJvcGljX2NsdXN0ZXI1LCAnTmVvdHJvcGljIGNsdXN0ZXIgNScpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnbmVvdHJvcGljX2NsdXN0ZXI1LmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChuZW90cm9waWNfY2x1c3RlcjUpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KG5lb3Ryb3BpY19jbHVzdGVyNSksIHVuaXRzID0gIm1tIikKYGBgCgojIyBQYWxlYXJjdGljCmBgYHtyfQpwYWxlYXJjdGljX2NpdGllc19jb21tdW5pdHlfZGF0YSA9IGNvbW11bml0eV9kYXRhX21ldHJpY3MgJT4lIGZpbHRlcihjb3JlX3JlYWxtID09ICdQYWxlYXJjdGljJykKcGFsZWFyY3RpY19jaXRpZXNfY29tbXVuaXR5X2RhdGEgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9uYW1lKSAlPiUgZGlzdGluY3QoKSAlPiUgYXMubGlzdCgpCmBgYAoKYGBge3J9CnBhbGVhcmN0aWNfY2l0aWVzX25tZHMgPSBjb21tdW5pdHlfbm1kcyhjb21tdW5pdGllcyAlPiUgZmlsdGVyKGNpdHlfaWQgJWluJSBwYWxlYXJjdGljX2NpdGllc19jb21tdW5pdHlfZGF0YSRjaXR5X2lkKSkgCnBhbGVhcmN0aWNfY2l0aWVzX25tZHMKYGBgCgpgYGB7cn0Kc2NyZWVfcGxvdChwYWxlYXJjdGljX2NpdGllc19ubWRzKQpgYGAKCmBgYHtyfQpwYWxlYXJjdGljX2NpdGllcyA9IGNsdXN0ZXJfY2l0aWVzKGNpdHlfbm1kcyA9IHBhbGVhcmN0aWNfY2l0aWVzX25tZHMsIGNpdGllc19jb21tdW5pdHlfZGF0YSA9IHBhbGVhcmN0aWNfY2l0aWVzX2NvbW11bml0eV9kYXRhLCBjZW50ZXJzID0gNykKYGBgCgpgYGB7cn0KcGxvdF9ubWRzX2NsdXN0ZXJzKHBhbGVhcmN0aWNfY2l0aWVzKQpgYGAKCmBgYHtyfQpwYWxlYXJjdGljX2Jpb21lcyA9IHN0X2Nyb3AocmVzb2x2ZVtyZXNvbHZlJFJFQUxNID09ICdQYWxlYXJjdGljJyxjKCdSRUFMTScpXSwgeG1pbiA9IC0zMCwgeW1pbiA9IDIwLCB4bWF4ID0gODAsIHltYXggPSA2NSkKIApnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IHBhbGVhcmN0aWNfYmlvbWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSkpICsgCiAgZ2VvbV9zZihkYXRhID0gcGFsZWFyY3RpY19jaXRpZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5LCBjb2xvciA9IGNsdXN0ZXIpKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ3BhbGVhcmN0aWNfY2x1c3RlcnMuanBnJykpCmBgYAoKYGBge3J9CnRlc3RfdmFsdWUoJ1BhbGVhcmN0aWMgTU5URCcsIHBhbGVhcmN0aWNfY2l0aWVzJG1udGRfbm9ybWFsaXNlZCkKdGVzdF92YWx1ZSgnUGFsZWFyY3RpYyBiZWFrIGdhcGUnLCBwYWxlYXJjdGljX2NpdGllcyRnYXBlX3dpZHRoX2ZkaXZfbm9ybWFsaXNlZCkKdGVzdF92YWx1ZSgnUGFsZWFyY3RpYyBraXBwcyBkaXN0YW5jZScsIHBhbGVhcmN0aWNfY2l0aWVzJGtpcHBzX2Rpc3RhbmNlX2ZkaXZfbm9ybWFsaXNlZCkKYGBgCgojIyMgUGFsZWFyY3RpYyBDbHVzdGVyIDEKYGBge3J9CnBhbGVhcmN0aWNfY2x1c3RlcjEgPSBwYWxlYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkKcGxvdF9jaXR5X2NsdXN0ZXIocGFsZWFyY3RpY19jbHVzdGVyMSwgJ1BhbGVhcmN0aWMgY2x1c3RlciAxJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdwYWxlYXJjdGljX2NsdXN0ZXIxLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChwYWxlYXJjdGljX2NsdXN0ZXIxKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChwYWxlYXJjdGljX2NsdXN0ZXIxKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBQYWxlYXJjdGljIENsdXN0ZXIgMgpgYGB7cn0KcGFsZWFyY3RpY19jbHVzdGVyMiA9IHBhbGVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKQpwbG90X2NpdHlfY2x1c3RlcihwYWxlYXJjdGljX2NsdXN0ZXIyLCAnUGFsZWFyY3RpYyBjbHVzdGVyIDInKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ3BhbGVhcmN0aWNfY2x1c3RlcjIuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKHBhbGVhcmN0aWNfY2x1c3RlcjIpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KHBhbGVhcmN0aWNfY2x1c3RlcjIpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIFBhbGVhcmN0aWMgQ2x1c3RlciAzCmBgYHtyfQpwYWxlYXJjdGljX2NsdXN0ZXIzID0gcGFsZWFyY3RpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDMpCnBsb3RfY2l0eV9jbHVzdGVyKHBhbGVhcmN0aWNfY2x1c3RlcjMsICdQYWxlYXJjdGljIGNsdXN0ZXIgMycpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAncGFsZWFyY3RpY19jbHVzdGVyMy5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgocGFsZWFyY3RpY19jbHVzdGVyMyksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQocGFsZWFyY3RpY19jbHVzdGVyMyksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgUGFsZWFyY3RpYyBDbHVzdGVyIDQKYGBge3J9CnBhbGVhcmN0aWNfY2x1c3RlcjQgPSBwYWxlYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNCkKcGxvdF9jaXR5X2NsdXN0ZXIocGFsZWFyY3RpY19jbHVzdGVyNCwgJ1BhbGVhcmN0aWMgY2x1c3RlciA0JykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdwYWxlYXJjdGljX2NsdXN0ZXI0LmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChwYWxlYXJjdGljX2NsdXN0ZXI0KSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChwYWxlYXJjdGljX2NsdXN0ZXI0KSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBQYWxlYXJjdGljIENsdXN0ZXIgNQpgYGB7cn0KcGFsZWFyY3RpY19jbHVzdGVyNSA9IHBhbGVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSA1KQpwbG90X2NpdHlfY2x1c3RlcihwYWxlYXJjdGljX2NsdXN0ZXI1LCAnUGFsZWFyY3RpYyBjbHVzdGVyIDUnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ3BhbGVhcmN0aWNfY2x1c3RlcjUuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKHBhbGVhcmN0aWNfY2x1c3RlcjUpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KHBhbGVhcmN0aWNfY2x1c3RlcjUpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIFBhbGVhcmN0aWMgQ2x1c3RlciA2CmBgYHtyfQpwYWxlYXJjdGljX2NsdXN0ZXI2ID0gcGFsZWFyY3RpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDYpCnBsb3RfY2l0eV9jbHVzdGVyKHBhbGVhcmN0aWNfY2x1c3RlcjYsICdQYWxlYXJjdGljIGNsdXN0ZXIgNicpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAncGFsZWFyY3RpY19jbHVzdGVyNi5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgocGFsZWFyY3RpY19jbHVzdGVyNiksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQocGFsZWFyY3RpY19jbHVzdGVyNiksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgUGFsZWFyY3RpYyBDbHVzdGVyIDcKYGBge3J9CnBhbGVhcmN0aWNfY2x1c3RlcjcgPSBwYWxlYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNykKcGxvdF9jaXR5X2NsdXN0ZXIocGFsZWFyY3RpY19jbHVzdGVyNywgJ1BhbGVhcmN0aWMgY2x1c3RlciA3JykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdwYWxlYXJjdGljX2NsdXN0ZXI3LmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChwYWxlYXJjdGljX2NsdXN0ZXI3KSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChwYWxlYXJjdGljX2NsdXN0ZXI3KSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIEFmcm90cm9waWMKYGBge3J9CmFmcm90cm9waWNfY2l0aWVzX2NvbW11bml0eV9kYXRhID0gY29tbXVuaXR5X2RhdGFfbWV0cmljcyAlPiUgZmlsdGVyKGNvcmVfcmVhbG0gPT0gJ0Fmcm90cm9waWMnKQphZnJvdHJvcGljX2NpdGllc19jb21tdW5pdHlfZGF0YSAlPiUgZHBseXI6OnNlbGVjdChjaXR5X25hbWUpICU+JSBkaXN0aW5jdCgpICU+JSBhcy5saXN0KCkKYGBgCgpgYGB7cn0KYWZyb3Ryb3BpY19jaXRpZXNfbm1kcyA9IGNvbW11bml0eV9ubWRzKGNvbW11bml0aWVzICU+JSBmaWx0ZXIoY2l0eV9pZCAlaW4lIGFmcm90cm9waWNfY2l0aWVzX2NvbW11bml0eV9kYXRhJGNpdHlfaWQpKSAKYWZyb3Ryb3BpY19jaXRpZXNfbm1kcwpgYGAKCmBgYHtyfQpzY3JlZV9wbG90KGFmcm90cm9waWNfY2l0aWVzX25tZHMpCmBgYAoKYGBge3J9CmFmcm90cm9waWNfY2l0aWVzID0gY2x1c3Rlcl9jaXRpZXMoY2l0eV9ubWRzID0gYWZyb3Ryb3BpY19jaXRpZXNfbm1kcywgY2l0aWVzX2NvbW11bml0eV9kYXRhID0gYWZyb3Ryb3BpY19jaXRpZXNfY29tbXVuaXR5X2RhdGEsIGNlbnRlcnMgPSAyKQpgYGAKCmBgYHtyfQpwbG90X25tZHNfY2x1c3RlcnMoYWZyb3Ryb3BpY19jaXRpZXMpCmBgYAoKYGBge3J9CmFmcm90cm9waWNfYmlvbWVzID0gcmVzb2x2ZVtyZXNvbHZlJFJFQUxNID09ICdBZnJvdHJvcGljJyxjKCdSRUFMTScpXQogCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gYWZyb3Ryb3BpY19iaW9tZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKyAKICBnZW9tX3NmKGRhdGEgPSBhZnJvdHJvcGljX2NpdGllcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnksIGNvbG9yID0gY2x1c3RlcikpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnYWZyb3Ryb3BpY19jbHVzdGVycy5qcGcnKSkKYGBgCgpgYGB7cn0KdGVzdF92YWx1ZSgnQWZyb3Ryb3BpYyBNTlREJywgYWZyb3Ryb3BpY19jaXRpZXMkbW50ZF9ub3JtYWxpc2VkKQp0ZXN0X3ZhbHVlKCdBZnJvdHJvcGljIGJlYWsgZ2FwZScsIGFmcm90cm9waWNfY2l0aWVzJGdhcGVfd2lkdGhfZmRpdl9ub3JtYWxpc2VkKQp0ZXN0X3ZhbHVlKCdBZnJvdHJvcGljIGtpcHBzIGRpc3RhbmNlJywgYWZyb3Ryb3BpY19jaXRpZXMka2lwcHNfZGlzdGFuY2VfZmRpdl9ub3JtYWxpc2VkKQpgYGAKCiMjIyBBZnJvdHJvcGljIENsdXN0ZXIgMQpgYGB7cn0KYWZyb3Ryb3BpY19jbHVzdGVyMSA9IGFmcm90cm9waWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAxKQpwbG90X2NpdHlfY2x1c3RlcihhZnJvdHJvcGljX2NsdXN0ZXIxLCAnQWZyb3Ryb3BpYyBjbHVzdGVyIDEnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2Fmcm90cm9waWNfY2x1c3RlcjEuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKGFmcm90cm9waWNfY2x1c3RlcjEpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KGFmcm90cm9waWNfY2x1c3RlcjEpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIEFmcm90cm9waWMgQ2x1c3RlciAyCmBgYHtyfQphZnJvdHJvcGljX2NsdXN0ZXIyID0gYWZyb3Ryb3BpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDIpCnBsb3RfY2l0eV9jbHVzdGVyKGFmcm90cm9waWNfY2x1c3RlcjIsICdBZnJvdHJvcGljIGNsdXN0ZXIgMicpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnYWZyb3Ryb3BpY19jbHVzdGVyMi5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgoYWZyb3Ryb3BpY19jbHVzdGVyMiksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQoYWZyb3Ryb3BpY19jbHVzdGVyMiksIHVuaXRzID0gIm1tIikKYGBgCgojIyBJbmRvbWFsYXlhbgpgYGB7cn0KaW5kb21hbGF5YW5fY2l0aWVzX2NvbW11bml0eV9kYXRhID0gY29tbXVuaXR5X2RhdGFfbWV0cmljcyAlPiUgZmlsdGVyKGNvcmVfcmVhbG0gPT0gJ0luZG9tYWxheWFuJykKaW5kb21hbGF5YW5fY2l0aWVzX2NvbW11bml0eV9kYXRhICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfbmFtZSkgJT4lIGRpc3RpbmN0KCkgJT4lIGFzLmxpc3QoKQpgYGAKCmBgYHtyfQppbmRvbWFsYXlhbl9jaXRpZXNfbm1kcyA9IGNvbW11bml0eV9ubWRzKGNvbW11bml0aWVzICU+JSBmaWx0ZXIoY2l0eV9pZCAlaW4lIGluZG9tYWxheWFuX2NpdGllc19jb21tdW5pdHlfZGF0YSRjaXR5X2lkKSkgCmluZG9tYWxheWFuX2NpdGllc19ubWRzCmBgYAoKYGBge3J9CnNjcmVlX3Bsb3QoaW5kb21hbGF5YW5fY2l0aWVzX25tZHMpCmBgYAoKYGBge3J9CmluZG9tYWxheWFuX2NpdGllcyA9IGNsdXN0ZXJfY2l0aWVzKGNpdHlfbm1kcyA9IGluZG9tYWxheWFuX2NpdGllc19ubWRzLCBjaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBpbmRvbWFsYXlhbl9jaXRpZXNfY29tbXVuaXR5X2RhdGEsIGNlbnRlcnMgPSA1KQpgYGAKCmBgYHtyfQpwbG90X25tZHNfY2x1c3RlcnMoaW5kb21hbGF5YW5fY2l0aWVzKQpgYGAKCmBgYHtyfQppbmRvbWFsYXlhbl9iaW9tZXMgPSByZXNvbHZlW3Jlc29sdmUkUkVBTE0gPT0gJ0luZG9tYWxheWFuJyxjKCdSRUFMTScpXQogCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gaW5kb21hbGF5YW5fYmlvbWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSkpICsgCiAgZ2VvbV9zZihkYXRhID0gaW5kb21hbGF5YW5fY2l0aWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSwgY29sb3IgPSBjbHVzdGVyKSkKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdpbmRvbWFsYXlhbl9jbHVzdGVycy5qcGcnKSkKYGBgCgpgYGB7cn0KdGVzdF92YWx1ZSgnSW5kb21hbGF5YW4gTU5URCcsIGluZG9tYWxheWFuX2NpdGllcyRtbnRkX25vcm1hbGlzZWQpCnRlc3RfdmFsdWUoJ0luZG9tYWxheWFuIGJlYWsgZ2FwZScsIGluZG9tYWxheWFuX2NpdGllcyRnYXBlX3dpZHRoX2ZkaXZfbm9ybWFsaXNlZCkKdGVzdF92YWx1ZSgnSW5kb21hbGF5YW4ga2lwcHMgZGlzdGFuY2UnLCBpbmRvbWFsYXlhbl9jaXRpZXMka2lwcHNfZGlzdGFuY2VfZmRpdl9ub3JtYWxpc2VkKQpgYGAKCiMjIyBJbmRvbWFsYXlhbiBDbHVzdGVyIDEKYGBge3J9CmluZG9tYWxheWFuX2NsdXN0ZXIxID0gaW5kb21hbGF5YW5fY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAxKQpwbG90X2NpdHlfY2x1c3RlcihpbmRvbWFsYXlhbl9jbHVzdGVyMSwgJ0luZG9tYWxheWFuIGNsdXN0ZXIgMScpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnaW5kb21hbGF5YW5fY2x1c3RlcjEuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKGluZG9tYWxheWFuX2NsdXN0ZXIxKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChpbmRvbWFsYXlhbl9jbHVzdGVyMSksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgSW5kb21hbGF5YW4gQ2x1c3RlciAyCmBgYHtyfQppbmRvbWFsYXlhbl9jbHVzdGVyMiA9IGluZG9tYWxheWFuX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMikKcGxvdF9jaXR5X2NsdXN0ZXIoaW5kb21hbGF5YW5fY2x1c3RlcjIsICdJbmRvbWFsYXlhbiBjbHVzdGVyIDInKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2luZG9tYWxheWFuX2NsdXN0ZXIyLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChpbmRvbWFsYXlhbl9jbHVzdGVyMiksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQoaW5kb21hbGF5YW5fY2x1c3RlcjIpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIEluZG9tYWxheWFuIENsdXN0ZXIgMwpgYGB7cn0KaW5kb21hbGF5YW5fY2x1c3RlcjMgPSBpbmRvbWFsYXlhbl9jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDMpCnBsb3RfY2l0eV9jbHVzdGVyKGluZG9tYWxheWFuX2NsdXN0ZXIzLCAnSW5kb21hbGF5YW4gY2x1c3RlciAzJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdpbmRvbWFsYXlhbl9jbHVzdGVyMy5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgoaW5kb21hbGF5YW5fY2x1c3RlcjMpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KGluZG9tYWxheWFuX2NsdXN0ZXIzKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBJbmRvbWFsYXlhbiBDbHVzdGVyIDQKYGBge3J9CmluZG9tYWxheWFuX2NsdXN0ZXI0ID0gaW5kb21hbGF5YW5fY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSA0KQpwbG90X2NpdHlfY2x1c3RlcihpbmRvbWFsYXlhbl9jbHVzdGVyNCwgJ0luZG9tYWxheWFuIGNsdXN0ZXIgNCcpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnaW5kb21hbGF5YW5fY2x1c3RlcjQuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKGluZG9tYWxheWFuX2NsdXN0ZXI0KSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChpbmRvbWFsYXlhbl9jbHVzdGVyNCksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgSW5kb21hbGF5YW4gQ2x1c3RlciA1CmBgYHtyfQppbmRvbWFsYXlhbl9jbHVzdGVyNSA9IGluZG9tYWxheWFuX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNSkKcGxvdF9jaXR5X2NsdXN0ZXIoaW5kb21hbGF5YW5fY2x1c3RlcjUsICdJbmRvbWFsYXlhbiBjbHVzdGVyIDUnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2luZG9tYWxheWFuX2NsdXN0ZXI1LmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChpbmRvbWFsYXlhbl9jbHVzdGVyNSksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQoaW5kb21hbGF5YW5fY2x1c3RlcjUpLCB1bml0cyA9ICJtbSIpCmBgYAoK